一、主從構造器
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家鬼",絕對的忠貞不二。
文章列表
留言列表