天天看點

Scala 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章
  • 檢視更多
  • A Tour of Scala: Extractor Objects

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方法,應該确認你的實作保留了等價關系
  • 檢視更多
  • How to Write an Equality Method in Java
  • Eric Torreborre shares an excellent canEqual example on GitHub
  • “Equivalence relation” defined on Wikipedia
  • The Scala Any class

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
        }
    }
               
  • 檢視更多
  • A Tour of Scala: Inner Classes

繼續閱讀