Cookbook读书笔记 Chapter 4.Classes and Properties 第二部分

4.8 分配块或函数给字段

  • 使用代码块或者调用一个函数初始化类里的字段

    4.8.1 解决方案

  • 设置字段等于需要的代码块或者函数

    class Foo {
    
        // set 'text' equal to the result of the block of code
        val text = {
            var lines = ""
            try {
                lines = io.Source.fromFile("/etc/passwd").getLines.mkString
            } catch {
                case e: Exception => lines = "Error happened"
            }
            lines
            }
            println(text)
    }
    
    object Test extends App {
        val f = new Foo
    }
    
  • 上面分配代码块给text字段和println语句都在类Foo的主体部分中,他们都是类的构造函数,当创建一个类的实例时他们会被执行。
  • 同样方式。分配方法或者函数给类字段

    class Foo {
        import scala.xml.XML
    
        // assign the xml field to the result of the load method
        val xml = XML.load("http://example.com/foo.xml")
    
        // more code here ...
    }
    

    4.8.2 讨论

  • 如果定义一个字段为lazy,意味着不会立马执行直到字段被获取调用:

    class Foo {
        val text =
            io.Source.fromFile("/etc/passwd").getLines.foreach(println)
    }
    
    object Test extends App {
        val f = new Foo
    }
    
  • 上面代码忽略潜在错误,如果运行在Unix系统,会打印/etc/passwd文件

    class Foo {
        lazy val text =
            io.Source.fromFile("/etc/passwd").getLines.foreach(println)
    }
    
    object Test extends App {
        val f = new Foo
    }
    
  • 上面声明成lazy字段的代码编译执行后,没有任何输出。因为text字段不会初始化直到它被获取。

4.9 设置未初始化的var字段类型

  • 问题: 想要设置一个未初始化var字段类型,所以开始写代码如下,然后如何完成表达式。

    var x =
    

    4.9.1 解决方案

  • 一般来说,定义字段为Option。对于具体的类型,比如String和numeric字段,可以指定默认的初始化值,下面的address字段可以定义成Option,初始化如下。

    case class Person(var username: String, var password: String) {
    
        var age = 0
        var firstName = ""
        var lastName = ""
        var address = None: Option[Address]
    }
    
    case class Address(city: String, state: String, zip: String)
    
  • 使用Some[Address]赋值

    val p = Person("alvinalexander", "secret")
    p.address = Some(Address("Talkeetna", "AK", "99676"))
    
  • 如想要获取address字段,有很多方法可见20.6章。可以使用foreach循环打印:

    p.address.foreach { a =>
        println(a.city)
        println(a.state)
        println(a.zip)
    }
    
  • 如果address未赋值,那么address是一个None,调用foreach没有问题,循环会自动跳出。如果已经赋值,那么address是一个Some[Address],循环会进入然后打印。

4.9.2 讨论

  • 很容易创建一个Int和Double字段

    var i = 0 // Int
    var d = 0.0 // Double
    
  • 上面例子中编译器会自动区分需要的类型。如果需要不同的数字类型,方法如下:

    var b: Byte = 0
    var c: Char = 0
    var f: Float = 0
    var l: Long = 0
    var s: Short = 0
    
  • 查看更多
  • Option class
  • 不要设置字段为null,更多见20.5章:“Eliminate null Values from Your Code”
  • 20.6章:“Using the Option/Some/None Pattern”

4.10 当继承类时处理构造函数参数

  • 问题:当继承一个基类时,需要处理基类声明的构造函数参数以及子类新的参数

    4.10.1 解决方案

  • 往常一样使用val或者var构造函数参数声明基类,当定义子类构造函数时,去掉两个类中相同字段前的val或者var声明,当定义子类新的构造函数参数时使用val或者var声明。

    class Person (var name: String, var address: Address) {
        override def toString = if (address == null) name else s"$name @ $address"
    }
    
    case class Address (city: String, state: String)
    
  • 子类Employee

    class Employee (name: String, address: Address, var age: Int) 
    extends Person (name, address) {
        // rest of the class
    }
    
  • 创建Employee实例

    val teresa = new Employee("Teresa", Address("Louisville", "KY"), 25)
    
  • 输出

    scala> teresa.name
    res0: String = Teresa
    
    scala> teresa.address
    res1: Address = Address(Louisville,KY)
    
    scala> teresa.age
    res2: Int = 25
    

4.10.2 讨论

  • 理解Scala编译器如何转换你的代码有助于理解子类构造函数参数如何工作,下面代码放到文件Person.scala中:

    case class Address (city: String, state: String)
    
    class Person (var name: String, var address: Address) {
        override def toString = if (address == null) name else s"$name @ $address"
    }
    
  • 上面字段是var变量,Scala编译器生成了获取器和修改器,编译Person.scala反编译Person.class如下:

    $ javap Person
    Compiled from "Person.scala"
    public class Person extends java.lang.Object implements scala.ScalaObject{
        public java.lang.String name();
        public void name_$eq(java.lang.String);
        public Address address();
        public void address_$eq(Address);
        public java.lang.String toString();
        public Person(java.lang.String, Address);
    }
    
  • 新的问题:如果定义一个Employee类继承Person,如何处理Employee构造函数里的name和address字段?假设没有新的参数,至少有两种选择:

    // Option 1: define name and address as 'var'
    class Employee (var name: String, var address: Address) 
    extends Person (name, address) { ... }
    
    // Option 2: define name and address without var or val
    class Employee (name: String, address: Address)
    extends Person (name, address) { ... }
    
  • 因为Scala已经为Person类里的name和address声明了getter和setter方法,解决方法是不使用var进行声明:

    // this is correct
    class Employee (name: String, address: Address) 
    extends Person (name, address) { ... }
    
  • 把下面代码编译反编译,文件名是Person.scala,反编译Employee.class

    case class Address (city: String, state: String)
    
    class Person (var name: String, var address: Address) {
        override def toString = if (address == null) name else s"$name @ $address"
    }
    
    class Employee (name: String, address: Address)
    extends Person (name, address) {
        // code here ...
    }
    
  • 反编译结果如下:

    $ javap Employee
    Compiled from "Person.scala"
    public class Employee extends Person implements scala.ScalaObject{
        public Employee(java.lang.String, Address);
    }
    
  • Employee继承Person,Scala不为name和address字段生成getter和setter方法,Employee类继承了Person类的行为。

4.11 调用超类构造函数

  • 问题:想要控制当创建子类构造函数时调用的父类构造函数

    4.11.1 解决方案

  • 这有一个问题,你可以控制子类的主构造函数调用的父类构造函数,但是不可以控制子类的辅助构造函数调用的父类构造函数。
  • 下面例子,定义一个Dog类去调用Animal类的主构造函数:

    class Animal (var name: String) {
        // ...
    }
    
    class Dog (name: String) extends Animal (name) {
        // ...
    }
    
  • 如果Animal类有多个构造函数,那么Dog类的主构造函数可以调用任意一个

    // (1) primary constructor
    class Animal (var name: String, var age: Int) {
    
        // (2) auxiliary constructor
        def this (name: String) {
            this(name, 0)
        }
        override def toString = s"$name is $age years old"
    }
    
    // calls the Animal one-arg constructor
    class Dog (name: String) extends Animal (name) {
        println("Dog constructor called")
    }
    
    // call the two-arg constructor
    class Dog (name: String) extends Animal (name, 0) {
        println("Dog constructor called")
    }
    

    4.11.2 辅助构造函数

  • 辅助构造函数的第一行必须调用当前类的另一个构造函数,不可能调用父类的构造函数

    case class Address (city: String, state: String)
    case class Role (role: String)
    
    class Person (var name: String, var address: Address) {
    
        // no way for Employee auxiliary constructors to call this constructor
        def this (name: String) {
            this(name, null)
            address = null
        }
    
        override def toString = if (address == null) name else s"$name @ $address"
    }
    
    class Employee (name: String, role: Role, address: Address)
    extends Person (name, address) {
    
        def this (name: String) {
            this(name, null, null)
        }
    
        def this (name: String, role: Role) {
            this(name, role, null)
        }
    
        def this (name: String, address: Address) {
            this(name, null, address)
        }
    
    }
    

4.12 使用抽象类(Abstract Class)

  • 问题:Scala有特质(trait),而且特质比抽象类更灵活,那么什么时候使用抽象类

    4.12.1 解决方案

  • 以下两点使用抽象类:
    • 创建一个需要构造函数参数的基类
    • Scala代码会被Java代码调用
  • 特质不允许有构造函数参数:

    // this won't compile
    trait Animal(name: String)
    
    // this compile
    abstract class Animal(name: String)
    
  • 17.7章解决特质实现的方法不能被Java代码调用的问题

4.12.2 讨论

  • 一个类智能继承一个抽象类。
  • 声明抽象方法:

    def speak // no body makes the method abstract
    
  • 抽象方法不需要使用abstract关键词,去除方法的body就会变成抽象方法。这和在特质里定义抽象方法是一致的。

    abstract class BaseController(db: Database) {
    
        def save { db.save }
        def update { db.update }
        def delete { db.delete }
    
        // abstract
        def connect
    
        // an abstract method that returns a String
        def getStatus: String
    
        // an abstract method that takes a parameter
        def setServerName(serverName: String)
    }
    
  • 子类继承之后需要实现抽象方法或者继续声明成抽象的,不实现方法会报出“class needs to be abstract”错误

    scala> class WidgetController(db: Database) extends BaseController(db)
    <console>:9: error: class WidgetController needs to be abstract, since:
    method setServerName in class BaseController of type (serverName: String)Unit
    is not defined
    method getStatus in class BaseController of type => String is not defined
    method connect in class BaseController of type => Unit is not defined
            class WidgetController(db: Database) extends BaseController(db)
                  ^
    
  • 因为类只能继承一个抽象类,当决定使用特质还是抽象类时一般使用特质,除非基类需要构造函数参数

4.13 在抽象基类(或特质)中定义属性

  • 问题:在抽象基类(或特质)中定义抽象或具体属性可供所有子类引用

    4.13.1 解决方案

  • 可以在抽象类或者特质里声明val和var字段。这些字段可以是抽象或者有具体实现。

    4.13.2 抽象的val和var字段

  • 下面抽象类有抽象的val和var字段,一个简单的具体方法:

    abstract class Pet (name: String) {
        val greeting: String
        var age: Int
        def sayHello { println(greeting) }
        override def toString = s"I say $greeting, and I'm $age"
    }
    
  • 子类继承抽象类,然后为抽象的字段赋值,注意这些字段还是指定成val或者var:

    class Dog (name: String) extends Pet (name) {
        val greeting = "Woof"
        var age = 2
    }
    
    class Cat (name: String) extends Pet (name) {
        val greeting = "Meow"
        var age = 5
    }
    
  • object中演示调用:

    object AbstractFieldsDemo extends App {
        val dog = new Dog("Fido")
        val cat = new Cat("Morris")
    
        dog.sayHello
        cat.sayHello
    
        println(dog)
        println(cat)
    
        // verify that the age can be changed
        cat.age = 10
        println(cat)
    }
    
  • 结果输出:

    Woof
    Meow
    I say Woof, and I'm 2
    I say Meow, and I'm 5
    I say Meow, and I'm 10
    

4.13.3 讨论

  • 抽象类(或特质)里抽象字段的运行如下:
    • 一个抽象的var字段会自动生成getter和setter方法
    • 一个抽象的val字段会自动生成getter方法
    • 当在抽象类或特质里定义一个抽象字段,Scala编译器不会在结果代码里创建一个字段,只会根据val或者var生成相应的方法
  • 上面的代码通过 scalac -Xprint:all,或者反编译Pet.class文件,会发现没有greeting或者age字段。反编译输出如下:

    import scala.*;
    import scala.runtime.BoxesRunTime;
    
    public abstract class Pet
    {
        public abstract String greeting();
        public abstract int age();
        public abstract void age_$eq(int i);
    
        public void sayHello() {
            Predef$.MODULE$.println(greeting());
        }
    
        public String toString(){
            // code omitted
        }
        public Pet(String name){}
    }
    
  • 所以当你在具体的类里给这些字段提供具体的值时,必须重新定义字段为val或者var。因为在抽象类或者特质里这些字段实际并不存在,所以override关键词并不需要。
  • 另一个结果,可以在抽象基类使用def定义无参取代使用val定义,然后可以在具体类里定义成val。

    abstract class Pet (name: String) {
        def greeting: String
    }
    
    class Dog (name: String) extends Pet (name) {
        val greeting = "Woof"
    }
    
    object Test extends App {
        val dog = new Dog("Fido")
        println(dog.greeting)
    }
    

    4.13.4 抽象类里具体的val字段

  • 抽象类里定义一个具体的val字段可以提供一个初始化值,然后可以在具体子类重写那个值。

    abstract class Animal {
        val greeting = "Hello" // provide an initial value
        def sayHello { println(greeting) }
        def run
    }
    
    class Dog extends Animal {
        override val greeting = "Woof" // override the value
        def run { println("Dog is running") }
    }
    
  • 上面例子中,两个类中都创建了greeting字段

    abstract class Animal {
        val greeting = { println("Animal"); "Hello" }
    }
    
    class Dog extends Animal {
        override val greeting = { println("Dog"); "Woof" }
    }
    
    object Test extends App {
        new Dog
    }
    
  • 结果输出:

    Animal
    Dog
    
  • 可以反编译Animal和Dog类,greeting字段声明成如下:

    private final String greeting = "Hello";
    
  • 抽象类中字段声明成final val那么具体子类中就不能重写这个字段的值:

    abstract class Animal {
        final val greeting = "Hello" // made the field 'final'
    }
    
    class Dog extends Animal {
        val greeting = "Woof" // this line won't compile
    }
    

4.13.5 抽象类里具体var字段

  • 可以在抽象类或特质为var字段提供一个初始化值,然后在具体子类引用:

    abstract class Animal {
        var greeting = "Hello"
        var age = 0
        override def toString = s"I say $greeting, and I'm $age years old."
    }
    
    class Dog extends Animal {
        greeting = "Woof" //调用setter方法
        age = 2
    }
    
  • 这些字段在抽象基类里声明并赋值,反编译Animal类如下:

    private String greeting;
    private int age;
    
    public Animal(){
        greeting = "Hello";
        age = 0;
    }
    
    // more code ...
    
  • 因为在Animal基类里这个字段已经声明并且初始化,所以在具体子类里没有必要重新声明字段。
  • Dog类使用 scalac -Xprint:all 编译:

    class Dog extends Animal {
        def <init>(): Dog = {
            Dog.super.<init>();
            Dog.this.greeting_=("Woof");
            Dog.this.age_=(2);
            ()
        }
    }
    
  • 因为这个字段在抽象类里是具体的,他们只需要在具体子类里重新赋值即可

4.13.6 不要使用null

  • 使用Option/Some/None模式初始化字段:

    trait Animal {
        val greeting: Option[String]
        var age: Option[Int] = None
        override def toString = s"I say $greeting, and I'm $age years old."
    }
    
    class Dog extends Animal {
        val greeting = Some("Woof")
        age = Some(2)
    }
    
    object Test extends App {
        val d = new Dog
        println(d)
    }
    
  • 输出如下:

    I say Some(Woof), and I'm Some(2) years old.
    

4.14 Case类生成样本代码

  • 问题: 在match表达式。actor或者其他使用case类生成样本代码的情况,生成包括获取器,修改器,apply,unapply,toString, equals和hashCode等等方法。

4.14.1 解决方案

  • 定义一个case类如下:

    // name and relation are 'val' by default
    case class Person(name: String, relation: String)
    
  • 定义一个case类会生成很多样本代码,有以下好处:
    • 生成apply方法,所以不需要使用new关键词去创建这个类的实例
    • case类构造函数参数默认声明成val,会自动生成获取器方法,声明成var会自动生成获取器和修改器
    • 生成默认的toString方法
    • 生成unapply方法,可以在匹配表达式轻松使用case类
    • 生成equals和hashCode方法
    • 生成copy方法
  • 定义case类,创建一个新的实例时不需要使用new关键词

    scala> case class Person(name: String, relation: String)
    defined class Person
    
    // "new" not needed before Person
    scala> val emily = Person("Emily", "niece")
    emily: Person = Person(Emily,niece)
    
  • 构造函数默认声明成val,所以会自动生成获取器方法,但不会生成修改器方法:

    scala> emily.name
    res0: String = Emily
    
    scala> emily.name = "Fred"
    <console>:10: error: reassignment to val
        emily.name = "Fred"
                   ^
    
  • 构造函数参数声明成var,会自动生成获取器和修改器方法:

    scala> case class Company (var name: String)
    defined class Company
    
    scala> val c = Company("Mat-Su Valley Programming")
    c: Company = Company(Mat-Su Valley Programming)
    
    scala> c.name
    res0: String = Mat-Su Valley Programming
    
    scala> c.name = "Valley Programming"
    c.name: String = Valley Programming
    
  • Case类有一个默认的toString方法实现:

    scala> emily
    res0: Person = Person(Emily,niece)
    
  • 自动生成提取器方法(unapply),当需要在匹配表达式提取信息时很好用(构造器从给定的参数列表创建一个对象, 而提取器却是从传递给它的对象中提取出构造该对象的参数):

    scala> emily match { case Person(n, r) => println(n, r) }
    (Emily,niece)
    
  • 自动生成equals和hashCode方法,实例可以如下方法比较:

    scala> val hannah = Person("Hannah", "niece")
    hannah: Person = Person(Hannah,niece)
    
    scala> emily == hannah
    res1: Boolean = false
    
  • 自动创建copy方法,当需要clone一个对象时很有帮助,在运行过程中还可以改变一些字段:

    scala> case class Employee(name: String, loc: String, role: String)
    defined class Employee
    
    scala> val fred = Employee("Fred", "Anchorage", "Salesman")
    fred: Employee = Employee(Fred,Anchorage,Salesman)
    
    scala> val joe = fred.copy(name="Joe", role="Mechanic")
    joe: Employee = Employee(Joe,Anchorage,Mechanic)
    

4.14.2 讨论

  • case类主要目的是创建“不可变的记录”,这样可以很容易的在模式匹配表达式里使用。

    4.14.3 生成的代码

  • 文件Person.scala:

    case class Person(var name: String, var age: Int)
    
  • 编译后会创建两个class文件,Person.class和Person$.class

    $ scalac Person.scala
    
  • 反编译Person.class

    $ javap Person
    
    //结果
    Compiled from "Person.scala"
    public class Person extends java.lang.Object ↵
    implements scala.ScalaObject,scala.Product,scala.Serializable{
        public static final scala.Function1 tupled();
        public static final scala.Function1 curry();
        public static final scala.Function1 curried();
        public scala.collection.Iterator productIterator();
        public scala.collection.Iterator productElements();
        public java.lang.String name();
        public void name_$eq(java.lang.String);
        public int age();
        public void age_$eq(int);
        public Person copy(java.lang.String, int);
        public int copy$default$2();
        public java.lang.String copy$default$1();
        public int hashCode();
        public java.lang.String toString();
        public boolean equals(java.lang.Object);
        public java.lang.String productPrefix();
        public int productArity();
        public java.lang.Object productElement(int);
        public boolean canEqual(java.lang.Object);
        public Person(java.lang.String, int);
    }
    
  • 反编译Person$.class

    $ javap Person$
    
    //结果
    Compiled from "Person.scala"
    public final class Person$ extends scala.runtime.AbstractFunction2 ↵
    implements scala.ScalaObject,scala.Serializable{
        public static final Person$ MODULE$;
        public static {};
        public final java.lang.String toString();
        public scala.Option unapply(Person);
        public Person apply(java.lang.String, int);
        public java.lang.Object readResolve();
        public java.lang.Object apply(java.lang.Object,java.lang.Object);
    }
    
  • 去掉case,然后编译反编译如下:

    public class Person extends java.lang.Object{
        public java.lang.String name();
        public void name_$eq(java.lang.String);
        public int age();
        public void age_$eq(int);
        public Person(java.lang.String, int);
    }
    
  • 如果不需要那么多额外的函数,考虑使用正常的类。如果只想创建一个不适用new关键词创建实例的类,如下使用:

    val p = Person("Alex")
    
  • 此时,可以创建一个apply方法。详细看6.8章

  • 查看更多


4.15 定义一个equals方法(对象相等)

  • 问题: 类中定义一个equals方法比较对象实例

4.15.1 解决方案

  • 和Java一样,定义一个equals(和hashCode)方法比较两个实例,和Java不同的是,然后可以使用 == 方法比较两个实例是否相等。

    class Person (name: String, age: Int) {
    
        def canEqual(a: Any) = a.isInstanceOf[Person]
    
        override def equals(that: Any): Boolean =
            that match {
                case that: Person => that.canEqual(this) && this.hashCode == that.hashCode
                case _ => false
        }
    
        override def hashCode:Int = {
            val prime = 31
            var result = 1
            result = prime * result + age;
            result = prime * result + (if (name == null) 0 else name.hashCode)
            return result
        }
    
    }
    
  • 上面例子显示的是一个修改后的hashCode方法。
  • 使用 == 方法比较两个实例:

    import org.scalatest.FunSuite
    
    class PersonTests extends FunSuite {
    
        // these first two instances should be equal
        val nimoy = new Person("Leonard Nimoy", 82)
        val nimoy2 = new Person("Leonard Nimoy", 82)
        val shatner = new Person("William Shatner", 82)
        val ed = new Person("Ed Chigliak", 20)
    
        // all tests pass
        test("nimoy == nimoy") { assert(nimoy == nimoy) }
        test("nimoy == nimoy2") { assert(nimoy == nimoy2) }
        test("nimoy2 == nimoy") { assert(nimoy2 == nimoy) }
        test("nimoy != shatner") { assert(nimoy != shatner) }
        test("shatner != nimoy") { assert(shatner != nimoy) }
        test("nimoy != null") { assert(nimoy != null) }
        test("nimoy != String") { assert(nimoy != "Leonard Nimoy") }
        test("nimoy != ed") { assert(nimoy != ed) }
    
    }
    
    • 上面的测试创建在ScalaTest FunSuite,和JUnit单元测试类似

4.15.2 讨论

  • Java中 == 操作符比较引用相等,Scala中 == 是比较两个实例是否相等的方法。
  • 当使用继承时依旧可以继续使用上面的方法

    class Employee(name: String, age: Int, var role: String)
    extends Person(name, age)
    {
        override def canEqual(a: Any) = a.isInstanceOf[Employee]
    
        override def equals(that: Any): Boolean =
            that match {
                case that: Employee => 
                    that.canEqual(this) && this.hashCode == that.hashCode
                case _ => false  
        } 
        //上面case that: Employee保证that是Employee类型,that.canEqual(this)保证this也是Employee类型
    
        override def hashCode:Int = {
            val ourHash = if (role == null) 0 else role.hashCode
            super.hashCode + ourHash
        }
    }
    
  • 上面的代码使用canEqual,equals,hashCode相同方式,而且是一致的,尤其是比较子类实例和其父类实例

    class EmployeeTests extends FunSuite with BeforeAndAfter {
    
        // these first two instance should be equal
        val eNimoy1 = new Employee("Leonard Nimoy", 82, "Actor")
        val eNimoy2 = new Employee("Leonard Nimoy", 82, "Actor")
        val pNimoy = new Person("Leonard Nimoy", 82)
        val eShatner = new Employee("William Shatner", 82, "Actor")
    
        test("eNimoy1 == eNimoy1") { assert(eNimoy1 == eNimoy1) }
        test("eNimoy1 == eNimoy2") { assert(eNimoy1 == eNimoy2) }
        test("eNimoy2 == eNimoy1") { assert(eNimoy2 == eNimoy1) }
        test("eNimoy != pNimoy") { assert(eNimoy1 != pNimoy) }
        test("pNimoy != eNimoy") { assert(pNimoy != eNimoy1) }
    }
    

    4.15.3 理论

  • Scaladoc表述:“这个方法的任何实现都应该是等价关系”,等价关系应该有以下3个特征:
    • 自反性(reflexive):Any类型的实例x,x.equals(x)返回true
    • 对称性(symmetric):Any类型的实例x和y,x.equals(y)和y.equals(x)返回true
    • 传递性(transitive):AnyRef的实例x,y和z,如果x.equals(y)和y.equals(z)返回true,那么x.equals(z)也返回true
  • 因此如果重写equals方法,应该确认你的实现保留了等价关系

  • 查看更多


4.16 创建内部类

  • 希望创建一个类作为内部类并且保持在公开API之外,或者否则封装你的代码

    4.16.1 解决方案

  • 在一个类里面声明另一个类

    class PandorasBox {
    
        case class Thing (name: String)
    
        var things = new collection.mutable.ArrayBuffer[Thing]()
        things += Thing("Evil Thing #1")
        things += Thing("Evil Thing #2")
    
        def addThing(name: String) { things += new Thing(name) }
    }
    
  • PandorasBox类的使用者不需要担心Thing的实现就能获得things集合

    object ClassInAClassExample extends App {
        val p = new PandorasBox
    
        p.addThing("Evil Thing #3")
        p.addThing("Evil Thing #4")
    
        p.things.foreach(println)
    }
    

    4.16.2 讨论

  • Scala和Java不同,“不同于Java语言内部类是封闭类的成员,Scala中内部类和外部对象(object)绑定”:

    object ClassInObject extends App {
    
        // inner classes are bound to the object
        val oc1 = new OuterClass
        val oc2 = new OuterClass
        val ic1 = new oc1.InnerClass
        val ic2 = new oc2.InnerClass
        ic1.x = 10
        ic2.x = 20
        println(s"ic1.x = ${ic1.x}")
        println(s"ic2.x = ${ic2.x}")
    }
    
    class OuterClass {
        class InnerClass {
            var x = 1
        }
    }
    
  • 因为内部类绑定到他们的对象实例,打印如下:

    ic1.x = 10
    ic2.x = 20
    
  • 更多用法,对象里包括类,类里包括对象:

    object InnerClassDemo2 extends App {
    
        // class inside object
        println(new OuterObject.InnerClass().x)
    
        // object inside class
        println(new OuterClass().InnerObject.y)
    }
    
    object OuterObject {
        class InnerClass {
            var x = 1
        }
    }
    
    class OuterClass {
        object InnerObject {
            val y = 2
        }
    }
    
  • 查看更多