一、前言
前面學習了控制結構,下面學習Scala的Class和Properties。
二、Class&Properties
盡管Scala和Java很類似,但是對類的定義、類構造函數、字段可見性控制等則不相同,Java更為冗長,Scala精煉。本章將通過Scala的構造函數工作原理來理解Scala的類和字段,當申明類構造函數參數和使用var、val、private關鍵字來修飾類的字段時,Scala編譯器將會為你生成代碼,根據字段修飾符不同,Scala編譯器會生成不同的存取函數,本章也會展示如何重寫這些函數,另外,Scala將會根據你的值為變量賦予相應的類型。
2.1 創建主構造函數
1. 問題描述
你需要為一個類創建主構造函數,你很快就會發現其和Java大不相同。
2. 解決方案
Scala的主構造函數是如下的結合
· 構造函數參數
· 類主體中調用的方法
· 類主體中執行的語句和表達式
Scala類主體中聲明的字段與Java類似,他們在類初始化時被賦值,下面的類演示構造函數的參數、類字段和類中的語句
由於類主體中的方法也是構造函數的一部分,當創建一個Person實例時,你會看到println的輸出和printHome和printFullName函數調用的輸出
3 討論
如果你是從Java到Scala,你會發現在Scala中聲明一個主構造函數時非常不同的,在Java中,在不在主構造函數中都是非常明顯的,但是Scala模糊了這個區別,一旦你理解了這個方法,你將會更精確的定義類構造函數,由於構造函數中的firstName和lastName被聲明為var類型,因此其是可變的,Scala會為其生成存取函數
HOME字段時private和val的,其在Java中等效於final和private的,其不能被其他對象直接訪問,並且其值不能被改變,使用scalac進行編譯后,再用javap進行反編譯結果如下
可以看到包含了很多_$eq方法,這是var字段的語法糖,對用戶透明,對於如下類
class Person { var name = "" override def toString = s"name = $name" }
由於name是var類型的,因此Scala會為其生成存取函數,即將會生成name_$eq函數,有了該函數,你可以修改name字段
p.name = "Ron Artest"
實際上,你調用的是如下方法
p.name_$eq("Ron Artest")
可以看到Scala的代碼更為精煉,在聲明字段時需要注意Scala是否會生成該存取函數
2.2 控制構造函數字段的可見性
1. 問題描述
你想控制Scala中的類中作為構造函數參數的字段的可見性
2. 解決方案
構造函數中字段的可見性由該字段是否被聲明為val或var或無val、var,是否為字段添加了private決定,解決方案如下
· 如果字段被聲明為var,Scala會為其生成getter和setter方法
· 如果字段被聲明為val,Scala會為只其生成getter方法
· 如果字段無var或var,Scala將不會生成getter或setter方法
· var和val字段可以使用private關鍵字修飾,這樣可以防止生成getter或setter方法
如果字段被聲明為var,則表示該字段的值是可變的,所以Scala為其生成getter和setter函數,下面示例中的字段被聲明為var,則表示可變
如果字段被聲明為val,則表示該字段的值是不可變的,如同Java的final,因此,其不會有setter方法,但是有getter方法
當構造函數中的參數沒有使用var或者val聲明時,其可見性變得十分嚴格,Scala不會為其生成setter或getter方法
除了將變量定義為val或var之外,還可以使用private關鍵字修飾字段,private關鍵字可以防止Scala生成getter或setter方法,因此該字段只能在類內部訪問
在類外訪問name字段時,發生異常,而在類函數中訪問時正常
3. 討論
當字段定義為val時,則不會產生setter,若定義為var時,則會產生setter和getter,private關鍵可以提供其他的靈活性。case類中的構造器中的參數與上述情況不太相同,如果你沒有使用var或val修飾字段,如
case class Person(name: String)
此時你也可以訪問該字段,就像該字段被定義為val一樣
2.3 定義輔助構造器
1. 問題描述
你要定義類的一個或多個輔助構造函數,以便用戶不同方法來創建對象實例
2. 解決方案
將輔助構造函數定義為名字為this的類的方法,你可以定義多個輔助構造函數,但是它們需要有不同的函數簽名(參數列表),每個構造函數必須調用前面定義的多個構造函數中的一個。
class Pizza (var crustSize: Int, var crustType: String) { // one-arg auxiliary constructor def this(crustSize: Int) { this(crustSize, Pizza.DEFAULT_CRUST_TYPE) } // one-arg auxiliary constructor def this(crustType: String) { this(Pizza.DEFAULT_CRUST_SIZE, crustType) } // zero-arg auxiliary constructor def this() { this(Pizza.DEFAULT_CRUST_SIZE, Pizza.DEFAULT_CRUST_TYPE) } override def toString = s"A $crustSize inch pizza with a $crustType crust" } object Pizza { val DEFAULT_CRUST_SIZE = 12 val DEFAULT_CRUST_TYPE = "THIN" def main(args: Array[String]): Unit = { val p1 = new Pizza(Pizza.DEFAULT_CRUST_SIZE, Pizza.DEFAULT_CRUST_TYPE) val p2 = new Pizza(Pizza.DEFAULT_CRUST_SIZE) val p3 = new Pizza(Pizza.DEFAULT_CRUST_TYPE) val p4 = new Pizza println(p1) println(p2) println(p3) println(p4) } }
運行結果
A 12 inch pizza with a THIN crust A 12 inch pizza with a THIN crust A 12 inch pizza with a THIN crust A 12 inch pizza with a THIN crust
3. 討論
關鍵點如下
· 輔助構造函數時以this為名字的類方法
· 每個輔助構造函數必須以對先前定義的構造函數的調用開始
· 每個輔助構造函數必須有不同的函數簽名
· 構造函數通過this來調用其他構造函數
上述示例中的所有輔助構造函數都調用了主構造函數,但是不是只能調用主構造函數,還可調用輔助構造函數,如下所示
def this(crustType: String) { this(Pizza.DEFAULT_CRUST_SIZE) this.crustType = Pizza.DEFAULT_CRUST_TYPE }
同時,crustSize和crustType都是主構造函數中的參數,其可以使得Scala為其生成setter和getter函數,你也可以使用如下方法,其需要更多代碼
class Pizza () { var crustSize = 0 var crustType = "" def this(crustSize: Int) { this() this.crustSize = crustSize } def this(crustType: String) { this() this.crustType = crustType } // more constructors here ... override def toString = s"A $crustSize inch pizza with a $crustType crust" }
case類是一個可以生成很多樣板代碼的特殊類,為case類添加輔助構造器與常規類的做法不相同,這是因為它們不是真正的構造函數:它們是類的伴生對象中的apply
方法,為了進行驗證,先定義一個Person的case類
在val p = new Person("leesf", 25)代碼后,由於語法糖的存在,Scala編譯器將其轉化為了如下代碼
val p = Person.apply("leesf", 25)
這是伴生對象中的apply方法,如果你想要為case類添加構造函數,你只需要在伴生對象中添加apply方法即可
// the case class case class Person (var name: String, var age: Int) // the companion object object Person { def apply() = new Person("<no name>", 0) def apply(name: String) = new Person(name, 0) }
測試代碼如下
object CaseClassTest extends App { val a = Person() // corresponds to apply() val b = Person("leesf") // corresponds to apply(name: String) val c = Person("dyd", 25) println(a) println(b) println(c) // verify the setter methods work a.name = "leesf" a.age = 25 println(a) }
運行結果
Person(<no name>,0) Person(leesf,0) Person(dyd,25) Person(leesf,25)
2.4 定義私有主構造函數
1. 問題描述
你想要生成類私有的主構造函數,如強制執行單例模式
2. 解決方案
為了讓主構造器私有,需要在類名和構造函數所接受的任何參數之間插入private關鍵字
class Order private { ... } class Person private (name: String) { ... }
在REPL中,定義了私有類后,不能創建類實例
3. 討論
在Scala中強制單例模式的一種簡單方法就是使主構造器變為private,然后在伴生對象中添加getInstance方法
class Brain private { override def toString = "This is the brain." } object Brain { val brain = new Brain def getInstance = brain } object SingletonTest extends App { val brain = Brain.getInstance println(brain) }
運行結果:
This is the brain.
其中伴生類對象中的getInstance是靜態方法
在Java中,你可以在類中定義靜態方法來創建文件工具類,但在Scala中,你只需要將這些方法放在對象中即可
object FileUtils { def readFile(filename: String) = { // code here ... } def writeToFile(filename: String, contents: String) { // code here ... } }
說明:可以通過FileUtils.readFile和FileUtils.writeToFile來調用相應的方法
2.5 為構造函數參數提供默認值
1. 問題描述
你想要為構造器參數提供默認值,給其他類調用構造函數時可決定是否指定該參數
2. 解決方案
在構造函數聲明中給予參數默認值,這里有一個簡單的聲明,Socket類有一個構造函數參數名為timeout,默認值為10000
class Socket (val timeout: Int = 10000)
由於構造函數提供了默認參數,因此在調用時可以不指定timeout的值
也可以指定timeout的值
如果你更喜歡調用構造函數時使用命名參數的方法,你也可以用如下方法調用構造函數
3. 討論
默認參數可以有效消除輔助構造函數,如下的構造函數相當於兩個構造函數(不指定參數值和指定參數值)
class Socket (val timeout: Int = 10000)
若沒有默認參數這一特性,則需要定義一個輔助構造函數
class Socket(val timeout: Int) { def this() = this(10000) override def toString = s"timeout: $timeout" }
你可以為多參數的構造函數提供多個默認值
class Socket(val timeout: Int = 1000, val linger: Int = 2000) { override def toString = s"timeout: $timeout, linger: $linger" }
盡管你只定義了一個構造函數,但實際上有三個構造函數
在創建對象時你也可以提供參數名
2.6 覆蓋默認的訪問器和修改器
1. 問題描述
你想要覆蓋Scala為你生成的訪問器和修改器
2. 解決方案
這是一個技巧的問題,如果你想堅持用Scala的命名約定時,你不能重寫Scala為你生成的getter和setter方法,例如Person類有name參數,以下代碼將不會被編譯
為了解決這個問題,可以構造函數的字段名,如將name改為_name
name方法為訪問器,name_=方法為修改器
如果你不想使用Scala為你定義訪問器和修改器的名字,你可以自由更改,如改為getName、setName。
3. 討論
如果你將類構造器的字段設置為var,則Scala會自動生成getter和setter方法,對於如下類
class Stock (var symbol: String)
首先使用scalac進行編譯,然后使用javap Stock進行反編譯,結果如下
可以看到Scala生成了symbol方法和symbol_$eq方法,可以直接通過方法名調用,對於symbol_$eq而言,還可以使用symbol_=來調用
如果本想對字段加private,然而沒有加,如下類
class Stock (var _symbol: String) { def symbol = _symbol def symbol_= (s: String) { this.symbol = s println(s"symbol was updated, new value is $symbol") } }
同樣經過編譯和反編譯,結果如下
可以看到姿勢的_symbol函數和_symbol_$eq函數都變為public了,而加private修飾時,其結果如下
2.7 防止生成getter和setter方法
1. 問題描述
當你定義類的構造函數參數為var時,Scala將會自動生成setter和getter方法,若是val,則生成getter方法,但是你既不需要setter,也不需要getter
2. 解決方案
將字段使用private或者private[this]修飾,如下面示例
class Stock { var delayedPrice: Double = _ private var currentPrice: Double = _ }
使用scalac進行編譯,再使用javap進行反編譯,結果如下
可以看到delayedPrice字段生成了getter和setter方法,而currentPrice字段則沒有生成getter和setter方法
3. 討論
將字段定義為private,這樣其只能被同類的所有實例訪問,例如,所有的Stock實例可以互相訪問currentPrice字段,如下面示例的結果為true
將字段定義為private[this]將會使字段變為對象私有,只有包含它的對象能夠訪問,同類的實例將無法訪問其他實例的字段
可以看到在this對象的方法中無法訪問that對象的price字段
2.8 將代碼塊或函數賦值給字段
1. 問題描述
你想要使用代碼塊或通過調用函數初始化類中的字段
2. 解決方案
設置字段等於代碼塊或函數,如果算法需要運行一段時間,則可以設置為lazy,下面示例中,text被設置成等於代碼塊,它要么返回文件中包含的文本,要么返回錯誤信息,這取決於該文件是否存在和是否能被讀取
class Foo { // set 'text' equal to the result of the block of code val text = { var lines = "" try { lines = io.Source.fromFile("F:/test.txt").getLines.mkString } catch { case e: Exception => lines = "Error happened" } lines } println(text) } object Test extends App { val f = new Foo }
運行結果
This is a test file.
對text的賦值和println都在Foo類構造器中,當新生實例時會被執行,因此,編譯運行該示例時要么返回文件中的內容,要么發生錯誤,在catch中被捕獲
3. 討論
當字段被定義成lazy時,只有當被訪問時才會被初始化
class Foo { val text = io.Source.fromFile("F:/test.txt").getLines.foreach(println) } object Test extends App { val f = new Foo }
運行結果
This is a test file.
當字段使用lazy修飾時
class Foo { lazy val text = io.Source.fromFile("F:/test.txt").getLines.foreach(println) } object Test extends App { val f = new Foo }
無任何結果輸出
2.9 設置未初始化的var字段類型
1. 問題描述
你想要設置未初始化的var字段的類型,你可能會這樣寫,但是不知道如何結尾
var x =
2. 解決方案
通常,將字段定義為Option,對於有些類型,如String和numeric字段,你可以指明初始值,例如,你想要用戶在社交網中注冊,注冊使用到的信息只有username和password,因此,你可以有如下類定義
case class Person(var username: String, var password: String)
之后,你也需要用戶的其他信息,如age, first name, last name, address,可以有如下定義
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)
即使用Option[Address]類型的None作為缺省值,可以使用如下
val p = Person("leesf", "secret")
p.address = Some(Address("WH", "HB", "43000"))
當需要訪問address的字段時,可以如下
p.address.foreach { a =>
println(a.city)
println(a.state)
println(a.zip)
}
當p.address為None時,則會跳過,為Some時,則會打印相應信息
3. 討論
編譯器有時會自動給出類型,如下所示
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
2.10 處理繼承時的構造器參數
1. 問題描述
你想要擴展基類,並且需要使用基類中聲明的構造函數參數,以及子類中的新參數
2. 解決方案
還是使用var和val來定義構造函數參數,當定義子類構造函數時,父類構造函數中的字段不再使用var修飾,在子類構造函數中定義新的字段
class Person (var name: String, var address: Address) { override def toString = if (address == null) name else s"$name @ $address" }
首先定義了Person基類,然后定義Employee子類
class Employee (name: String, address: Address, var age: Int) extends Person (name, address) { // rest of the class }
Employee繼承Person類,不再使用var修飾name和address,但是age在父類中不存在,則使用var修飾,其中Address類如下
case class Address (city: String, state: String)
可以使用如下方法創建子類
val lee = new Employee("Leesf", Address("JZ", "HB"), 25)
3. 討論
為了理解構造函數參數在子類中是如何工作的,也可幫助理解Scala是如何翻譯你的代碼的,由於Person的參數被定義為var
class Person (var name: String, var address: Address) { override def toString = if (address == null) name else s"$name @ $address" }
Scala編譯器將會生成getter和setter
此時,如果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類中生成了setter和getter函數,一種解決辦法就是在Employee的構造函數的參數不用var修飾
// this is correct class Employee (name: String, address: Address) extends Person (name, address) { ... }
由於Employee的構造函數中並未使用var修飾,因此Scala不會為字段生成getter和setter方法,驗證代碼如下
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) { }
使用javap反編譯Employee,結果如下
可以看到Scala並未為Employee類生成任何setter和getter方法
2.11 調用父類構造函數
1. 問題描述
當創建子類構造函數時,你想控制父類構造函數
2. 解決方案
你可以通過子類的主構造函數來控制父類構造函數,但是不能通過子類的輔助構造函數來控制父類構造函數,當定義子類時,你可以通過定義extends部分來控制子類所調用的父類構造器,例如,Dog類被定義為調用Animal類,其有name參數
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") }
由於輔助構造函數的第一行必須要調用其他構造函數,因此,輔助構造函數不能調用父類構造函數,下面代碼中,Employee類的主構造含可以調用Person類的任意構造函數,但是Employee的輔助構造函數只能調用其他構造函數
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) } }
所以無法通過子類輔助構造函數來控制父類構造函數,因為輔助構造函數必須要調用其他構造函數,所有的輔助構造函數最終會調用主構造函數,最終由主構造函數調用輔助構造函數
2.12 何時使用抽象類
1. 問題描述
Scala有traits特性,並且traits比抽象類更靈活,那么何時使用抽象類呢
2. 解決方案
以下兩種原因需要使用抽象類
· 你想要創建一個包含構造函數參數的基類
· 代碼將會被Java代碼調用
考慮第一種情況,traits不允許構造函數參數,如下所示
trait Animal(name: String) // this won't compile
當基類構造函數必須含有參數時應該使用抽象類
abstract class Animal(name: String)
考慮第二種情況是你的代碼將會被Java調用,traits中實現的方法無法被Java訪問
3. 討論
當構造函數有參數時,需要使用抽象類而非traits,但是,一個類只能繼承一個抽象類,其與Java類似,當類中抽象方法時,該類就是抽象,方法體未定義的方法就是抽象方法
def speak // no body makes the method abstract
不需要使用abstract關鍵字,如下示例
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) }
其中connect、getStatus、setServerName為抽象方法,子類必須要實現或者是為抽象類
2.13 在抽象基類(或traits)中定義屬性
1. 問題描述
你想要在抽象類或traits中定義具體的或抽象屬性,以便所有的子類都可以引用
2. 解決方案
你可以在抽象類或traits中定義var或val字段,該字段可以是抽象的或者是具體定義的,下面示例展示了Pet抽象類中的抽象val和var字段,並且有一個sayHello的具體方法,以及重寫的toString方法
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" }
Dog和Cat類繼承Pet,並且提供了greeting和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 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
3. 討論
你可以在抽象類中定義val或var的抽象字段,抽象字段在抽象類中的工作機制非常有趣
· 抽象的var字段也會生成getter和setter方法
· 抽象的val字段會生成getter方法
· 當在抽象類或traits中定義抽象的var或val字段時,Scala並不會生成該字段,只會生成方法
當在具體類中重寫父類抽象的var或val字段時,一定要再次注明val或var,因為父類中不存在該字段,不需要使用override關鍵字,當在抽象類中使用def來定義無參數的方法時,可以在子類中定義為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) }
當在抽象類中定義具體的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
為了防止抽象類中的val字段被子類覆蓋,可以將其定義為final
abstract class Animal { final val greeting = "Hello" // made the field 'final' } class Dog extends Animal { val greeting = "Woof" // this line won't compile }
你也可以在抽象類中定義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" age = 2 }
greeting字段和age字段在Animal類中都被定義和初始化了,在Dog類中就沒有必要再重新聲明為var或val類型了
在上述討論中,不應該使用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.
2.14 使用樣例類生成樣板代碼
1. 問題描述
你需要使用樣例類來生成樣板代碼,如生成setter和getter方法,還有apply, unapply, toString, equals, hashCode等方法
2. 解決方案
將類定義成樣例類,並且添加相應的構造函數參數
case class Person(name: String, relation: String)
將類定義為樣例類后,會生成很多樣板代碼,如
· 生成apply方法,所以不需要是new關鍵字就可以創建實例
· 為構造函數參數生成getter方法,因為其默認是val的,當被定義為var時,會生成setter方法
· 會生成缺省的toString方法
· 生成unapply方法,在模式匹配中將會很方便
· 生成equals和hashCode方法
· 生成copy方法
3. 討論
樣例類在模式匹配中很實用,其默認參數為val,當創建樣例類時,Scala會為你生成很多樣板代碼
2.15 定義equals方法(對象相等)
1. 討論
你想要定義一個equals方法來比較不同對象之間的相等性
2. 解決方案
如同Java一樣,你在類中定義equals和hashCode來比較兩個對象,但與Java不同的是,你使用 == 來比較兩個對象的相等性,下面定義了equals方法和hashCode方法
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 } }
接着可以使用 == 來比較兩個Person對象是否相等
3. 討論
在Java中,==是比較兩個對象的引用是否相等,而在Scala中,其是根據用戶定義邏輯進行比較,其調用equals方法,在涉及繼承時,也可以使用如上方法
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 } override def hashCode:Int = { val ourHash = if (role == null) 0 else role.hashCode super.hashCode + ourHash } }
2.16 創建內部類
1. 問題描述
你希望創建一個內部類,以將類排除在公共API之外,或者封裝您的代碼
2. 解決方案
將類聲明在另一個類中,如下示例
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內部使用things集合
object ClassInAClassExample extends App { val p = new PandorasBox p.things.foreach(println) }
運行結果
Evil Thing #1
Evil Thing #2
並且可以調用addThing方法
p.addThing("Evil Thing #3")
p.addThing("Evil Thing #4")
3. 討論
與Java的內部類屬於外部類不同,Scala中的內部類綁定到外部類的對象上
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
三、總結
本篇博文學習了Scala中類的相關知識點,對比Java學習還是相對簡單,也謝謝各位園友的觀看~