
Scala Tutorial(6)Scala 物件導向基礎之二
Scala Tutorial(5)Scala 物件導向基礎之一 << 前情 我們之前看到了 Scala 在物件導向方面,提供了比 Java 更簡潔的語法來定義類別,並且提供了在程式語言層面所提供的 Singleton 物件,但 Scala 所提供的物件導向功能不只如此,而且在語義也許 Java 有一點點不同。如果想要了解 Scala 的特性,以便在寫 Scala 的程式碼時做出正確的設計決策的話,那麼知道 Scala 提供的這些語法是如何轉到 Java VM 上,將會很有幫助。 建構子中的參數詳解在 Scala 當中可以在宣告類別的 primary constructor 時宣告該類別的成員變數,像下面的程式碼一樣: class Person(name: String, age: Int) 我們說過上面這段程式碼會產生兩個成員變數,但實際上這是簡化過後的說法。Scala 的編譯器是相當聰明的,當他看到這段程式碼的時候,他會知道在 我們可以透過 [[syntax trees at end of cleanup]] // test.scala package <empty> { class Person extends Object { def <init>(name: String, age: Int): Person = { Person.super.<init>(); () } } } 同樣的,如果我們將程式碼新增一個 printAge 函式來印出 class Person(name: String, age: Int) { def printAge { println("Age:" + age) } println("Name:" + name) } 當我們針對上述的程式碼用 -print 參數進行檢查時,會發現與之前的版本不同,在 Person 這個類別當中出現了一個 但另一方面,如果我們在宣告 primary constructor 的參數列表的變數時加上了 val 或 var 前綴時,由於這些變數會變得可以透過外介存取,所以一定會成為成員變數,例如下面的範例程式碼: class Person(val name: String, var age: Int) 但故事並沒有那麼簡單,雖然說在這個情況下,看起來像是我們在 Person 這個類別裡宣告了一個 public 的 clss Person { val name: String = "" // 實際的內容會在建構子中指定 var age: Int = _ } 但實際上並不是這樣的,當我們在主建構子的參數列表的參數加了 val 變數時,除了宣告成員變數外,還會宣告成員變數的 Getter。同樣的,當我們加上 var 關鍵字時,除了成員變數、Getter 外,還會加上 Setter,所以實際上編譯出來的程式碼會接近下面這樣: class Person { private val mName: String = "" private var mAge: Int = 0 def name() = this.mName def age = this.mAge def age_= (x: Int): Unit = { this.mAge = x } } 在這邊我們可以看到幾個之前在介紹定義 method 時沒有講到的細節:
包山包海的 Case Class看完了簡單的類別定義後,現在來看看 Scala 提供的超方便的 Case Class 吧!在 Scala 中要定義 Case Class 相當的簡單,只要在 class 關鍵字前加上 class 就行了,當加上了 case 的關鍵字時,Scala 會幫我們自動實作一些常用的功能。 首先第一個就是當我們使用 case class 時,在主建構子中參數列表中的參數都會預設是 val 變數,可以被外界存取。(當然也可以自己加上 private 等修飾字,或是加上 var 成為 var 變數) case class Person(name: String, age: Int) val person = new Person("Joe", 13) println(person.age) 另一個就是自動產生有意義的 toString 函式,當我們使用一般的方式宣告類別時,toString 只會印出類別的名稱。但如果使用 case class 來宣告的話,則會印出該類別的主建構子中的參數列表宣告的成員變數的內容。 scala> class PersonA(name: String, age: Int) defined class PersonA scala> val joe = new PersonA("Joe", 13) joe: PersonA = PersonA@4be408f0 scala> case class Person(name: String, age: Int) defined class Person scala> val david = new Person("David", 15) david: Person = Person(David,15) 此外,它也會自動建立一個對應名稱的 Singleton 物件,並且實作我們之前提到的 scala> val joe = Person("Joe", 13) joe: Person = Person(Joe,13) scala> val david = Person.apply("David", 13) david: Person = Person(David,13) 另一個相當重要的功能是他會自動實作 equals() 和 hashCode() 方法,所以我們可以放心的將其放入各種 Java 或 Scala 的容器中,也可以直接使用 舉例而言,我們可以用 new 關鍵字建立兩個不同的物件,但含有相同的內容,這個時候用 hashCode 或 == 來檢查時,會發現兩者的內容是相同的: scala> val david1 = new Person ("David", 13) david1: Person = Person(David,13) scala> val david2 = new Person ("David", 13) david2: Person = Person(David,13) scala> david1.hashCode res8: Int = 254294983 scala> david2.hashCode res9: Int = 254294983 // 檢查兩個變數指到的物件的內容是否相同,語意與 Java 中的 equals 函式相同 scala> david1 == david2 res11: Boolean = true // 檢查兩個變數是否 reference 到同一個物件,語意和 Java 運作在 reference 變數上的 == 同同 scala> david1 eq david2 res12: Boolean = false 在這邊要特別提到的,是 case class 的 hashCode 函式是依據主建構子中的成員變數的內容決定的,所以如果我們將某個成員參數設成 var 變數的話,hashCode 很有可能會因為物件的狀態被改變而更動,如果在是在 hashCode 的一致性很重要的情境下,請務必記得這一點,並且避免將主建構子中的參數設成 var 變數。 scala> case class Person(name: String, var age: Int) defined class Person scala> val person = new Person("Joe", 13) person: Person = Person(Joe,13) scala> person.hashCode res0: Int = -87682959 scala> person.age = 15 person.age: Int = 15 scala> person.hashCode res1: Int = 710749424 除了上述提到的功能外,Case Class 還提供了一個叫做 copy 的函式,這個函式可以很方便地幫我們產生另一個物件,而其內容是建構在原本的物件上,但可以透過參數微調。 case class Person(name: String, age: Int) val user1 = new Person("Joe", 13) val user2 = user1.copy(age = 14) val user3 = user1.copy(name = "Billy", age = 30) println(user1) // user1 的物件狀態不會被改變 println(user2) // user2 是新建的物件,name 一樣但 age 被變動 println(user3) // 當然要把所有參數全部砍掉重練也是可以的 最後,case class 還實作了 unapply 這個函式,不過這個函式通常只有在使用 Scala 的 Pattern Matching 功能時會被 Scala 呼叫,我們將會在介紹 Pattern Matching 時回頭來介紹,在這邊就暫且按下不表。 小結在這一篇中,我們看到了 Scala 中的類別的主建構子的參數是如何運作的,以及 Scala 中所提供的 case class 如何進一步幫我簡化一些常見的工作。 但另一方面,我們一直都沒有提到與 Java 中的 Interface 相對應的東西。這是因為在 Scala 中提供的對應的 Trait 相較 Java 的 Interface 來說更為強大,舉例而言,在 Scala 中的 Trait 中的函式是可以有實作的,我們將會在下一篇中來詳細看看 Trait 要如何達成 Java 中 Interface 提供的功能,以及他有什麼其他神奇的地方。 |