zoukankan      html  css  js  c++  java
  • scala 学习笔记(04) OOP(上)主从构造器/私有属性/伴生对象(单例静态类)/apply方法/嵌套类

    一、主从构造器

    java中构造函数没有主、从之分,只有构造器重载,但在scala中,每个类都有一个主构造器,在定义class时,如果啥也没写,默认有一个xxx()的主构造器

    class Person {
    
      var name: String = _
    
      /**
       * 从构造器
       * @param name
       */
      def this(name: String) = {
        this //注意:从构造器,必须先调用主构造器
        this.name = name;
      }
    
      override def toString = {
        "name:" + name
      }
    
    }
    

    上面的这段代码,如果将生成的class反编译成java来看的话,可能更容易理解:

    public class Person
    {
      private String name;
    
      public String name()
      {
        return this.name; } 
      public void name_$eq(String x$1) { this.name = x$1; }
    
    
      public String toString()
      {
        return new StringBuilder().append("name:").append(name()).toString();
      }
    
      public Person()
      {
      }
    
      public Person(String name)
      {
        this();
        name_$eq(name);
      }
    }
    

    从反编译结果看,生成了二个构造函数,一个是默认的Person(),另一个是Person(String name)。

    Scala是一个崇尚简约之美的语言,在定义Class时,可以将属性声明、带参构造器,一并全解决了,所以刚才这段代码,"等效"于下面这样:

    class Person(var name: String) {
    
      override def toString = {
        "name:" + name
      }
    
    }
    

     注意第一行,此时的主构造器不再是默认的无参构造器,而是Person(var name:String),它有二层意思:一是定义了一个带参数的构造器,二是由于name前加了var,说明name:String不仅仅是构造器的参数,还是类Person的一个属性成员,不过这个版本与第一个版本还是有些差别的,如果用JD-GUI反编译查看的话,会发现默认的无参构造器消失了

    public class Person
    {
      private String name;
    
      public String name()
      {
        return this.name; } 
      public void name_$eq(String x$1) { this.name = x$1; }
    
      public String toString() {
        return new StringBuilder().append("name:").append(name()).toString();
      }
    
      public Person(String name)
      {
      }
    }
    

     Person的使用示例如下:

    object App {
    
      def main(args: Array[String]) {
        val p: Person = new Person("jimmy")
        println(p.toString)
        p.name = "jimmy.yang"
        println(p.toString)
      }
    
    }
    

    主构造器上,还可以增加限定词private来修饰,比如下面这样:

    class Person private(var name: String) {
    
      var age: Int = 0;
    
      def this(age: Int, name: String) = {
        this(name)
        this.age = age;
      }
    
      override def toString = {
        "name:" + name + ",age:" + age
      }
    
    }
    

    这样Person类的使用方,就只能使用从属构造器this(age:Int,name:String)了。

    二、私有属性(private property)

    将前面的Person改一下,将年龄Age设置成私有成员

    package yjmyzz
    
    
    class Person private(var name: String) {
    
      println("这是主构造器的可执行语句,我是" + name) //这一行在new Person时会执行
    
      /**
       * 定义一个私有成员
       */
      private var _age: Int = 0;
    
      def age = _age
    
    
      def this(age: Int, name: String) = {
        this(name)
        this._age = age;
      }
    
      def isOlder(another: Person) = {
        this._age > another._age //注意:这里可直接访问另一个Person的私有成员_age
      }
    
      override def toString = {
        "name:" + name + ",age:" + age
      }
    
    }
    

     注意:isOlder方法,该方法用于比较二个Person谁更年长,跟java不同的是,在Class定义范围内,可以直接访问另一个类实例的私有成员!这在java、c#中是绝对不允许的。

    另外,还有一个值得注意的地方,Class的定义里,除了def定义的方法(或许称为函数更适合)外,任何可执行语句都会被执行,比如第6行的println语句。下面是调用示例:

        val jimmy: Person = new Person(18, "jimmy")
        val mike: Person = new Person(30, "mike")
        if (mike.isOlder(jimmy))
          println(mike.name + " is older than " + jimmy.name)
    

    执行结果:

    这是主构造器的可执行语句,我是jimmy
    这是主构造器的可执行语句,我是mike
    mike is older than jimmy

    如果不希望private成员在Class定义中直接被其它实例所访问,可以改成private[this],即:

      private[this] var _age: Int = 0;
    
      def age = _age
    
      def isOlder(another: Person) = {
        this._age > another.age
      }
    

    这样的话,isOlder中的another,只能通过函数age来访问私有成员_age了。

    三、static成员/伴生对象Object/apply方法

    scala里并没有static关键字,要达到类似的效果,可以借助object对象,object天然是singleton模式,比如下面的代码:

    object Singleton {
    
      var count = 0;
    
      def increment: Unit = {
        count += 1
      }
    
    }
    

    定义成object类型的对象,没有办法直接new, object中的所有方法都是静态方法,这一点类似c#中的static静态类,使用时直接按静态方法调用即可:

        var obj1 = Singleton.count
        println(obj1)
        Singleton.increment
        var obj2 = Singleton.count
        println(obj2)
    

    object不仅仅用于单例模式的实现,更多时候,我们可以定义一个与class同名的object,然后把class的所有静态方法放到object里,比如:

    class People(var name: String) {
    
      println("main constructor in Class People")
    
    }
    
    
    object People {
    
      def whoami: Unit = {
        println("I am a people ")
      }
    
      def apply(name: String) = {
        println("apply in Object People")
        new People(name)
      }
    
    }
    

    后面的object People称为class People的伴生对象,可以理解为class People的静态成员/方法集合,注意里面的apply方法,这个方法会被自动调用,通常用于创建对象实例,有点工厂模式的意味,看下面的调用代码:

        var p:People = People("jimmy")
        println(p.name)
        People.whoami
    

    这里我们没有用new关键字来创建对象,而是"隐式"调用了伴生对象的静态方式apply,以下是输出结果:
    apply in Object People
    main constructor in Class People
    jimmy
    I am a people

    伴生对象+apply方法,这是scala中经常使用的一个技巧,即简化了代码,又起了工厂模式的作用,我们甚至还可以在apply方法中加入对象控制的额外业务逻辑,这比直接new对象更灵活。

    从object的使用上,还可以看出静态方法的调用上scala与java的不同,java中静态方法即可以用"类名.静态方法()",也可以用"对象实例.静态方法()"来调用,说实话,有点不太讲究,而Scala"纠正"了这一错误,静态方法只能在object(即:静态类)上调用,非静态方法只能在对象实例上调用,这与c#的理念是一致的(见:java学习:OOP入门 第7点)

    apply方法不仅可以存在于object中,class中也可以有apply方法,我们把People的Class改一下:

    class People(var name: String) {
    
      println("main constructor in Class People")
    
      def apply(): Unit ={
        println("apply in Class People")
      }
    
    }
    

    然后这么调用:

        var p:People = People("jimmy")
        p()
    

    注意第2行,就是这么简单!输出结果:
    apply in Object People
    main constructor in Class People
    apply in Class People

    四、内部类(也称嵌套类)

    class内部还可以再定义类,即嵌套类,与java不同的是,scala的嵌套类是属于实例的,而不属于定义它的外部类。这句话听着很绕,还是直接看代码吧,先把前面的People类改造一下:

    class People(var name: String) {
    
      /**
       * 定义嵌套类(注:必须写在最开始,好象也只能定义一个?)
       */
      master =>
    
      //这个master变量即指People本身this,名字可以随便取
    
      class Pet(var name: String) {
        def hi() = println("我叫" + this.name + " , 我是" + master.name + "的宠物!")
      }
    
    
      /**
       * 增加了一个宠物属性
       */
      var pet: Pet = _
    
    }
    
    
    object People {
    
      def apply(name: String, petName: String) = {
        println("apply in Object People")
        val people = new People(name)
        people.pet = new people.Pet(petName)
        people
      }
    
    }
    

     然后使用:

        val jimmy = new People("jimmy")
        val dog = new jimmy.Pet("wang wang") //注:这是调用的"实例"上的Pet,而不是new People.Pet()
        dog.hi()
    
        println("------------")
    
        val mike = People("Mike","miao miao")
        mike.pet.hi()
        println("------------")
    
    
        //println("jimmy与mike交换宠物:")
        //jimmy.pet = mike.pet //直接报错,因为mike的宠物是只属于mike的,它与jimmy的宠物类型不兼容
    
        //jimmy又养了一只猫
        var cat = new jimmy.Pet("miao")
        //然后把狗狗扔了
        jimmy.pet = cat;
        jimmy.pet.hi()
    

    注意第2行及第13行,第2行是直接用 new 实例.内部类()的方式创建的,而非 new 外部类.内部类()这种方式,说明内部类是从属于外部类的实例,第13行再次证明了这一点,虽然都是内部类Pet的实例,但当试图将mike的Pet实例赋值给jimmy的Pet实例时,编译器直接报错,说明内部类的实例一旦创建,则"生是X家人,死是X家鬼",绝对的忠贞不二。

  • 相关阅读:
    【转】linux tail命令使用方法详解
    机器学习:利用卷积神经网络实现图像风格迁移 (二)
    狄拉克函数(Dirac delta function)
    狄拉克函数(Dirac delta function)
    写作的积累 —— 台词
    写作的积累 —— 台词
    exponential family distribution(指数族分布)
    exponential family distribution(指数族分布)
    机器学习:利用卷积神经网络实现图像风格迁移 (一)
    十万个为什么 —— 古代没有拼音,怎么认字?
  • 原文地址:https://www.cnblogs.com/yjmyzz/p/4715339.html
Copyright © 2011-2022 走看看