嵌套類和內(nèi)部類
大部分時(shí)候,類被定義成一個(gè)獨(dú)立的程序單元。在某些情況下,也會(huì)把一個(gè)類放在另一個(gè)類的內(nèi)部定義,這個(gè)定義在其他類內(nèi)部的類就被稱為嵌套類(有的地方也叫寄生類),包含嵌套類的類被稱為外部類(有的地方也叫宿主類) 。
Java 的內(nèi)部類可分為兩種:靜態(tài)內(nèi)部類 (有 static 修飾)和非靜態(tài)內(nèi)部類(無(wú) static 修飾)。而 Kotlin 則完全取消了 static 修飾符。但實(shí)際上 Kotlin 也需要有靜態(tài)內(nèi)部類和非靜態(tài)內(nèi)部類之分,所以 Kotlin 只得為它們換了個(gè)“馬甲”。
- 嵌套類(相當(dāng)于靜態(tài)內(nèi)部類):只要將一個(gè)類放在另一個(gè)類中定義,這個(gè)類就變成了嵌套類,相當(dāng)于 Java 中有 static修飾的靜態(tài)內(nèi)部類。
- 內(nèi)部類(非靜態(tài)內(nèi)部類):使用 inner修飾的嵌套類叫內(nèi)部類,相當(dāng)于Java 中無(wú) static 修飾的非靜態(tài)內(nèi)部類 。
嵌套類主要有如下作用:
- 嵌套類提供了更好的封裝,可以把嵌套類隱藏在外部類之內(nèi),不允許同一個(gè)包中的其他類訪問(wèn)該類 。 假設(shè)需要?jiǎng)?chuàng)建 Cow 類, Cow 類需要組合一個(gè) CowLeg對(duì)象, CowLeg類只有在Cow 類中才有效,離開(kāi)了 Cow 類之后沒(méi)有任何意義。在這種情況下,就可以把 CowLeg 定義成 Cow 的嵌套類,不允許其他類訪問(wèn)CowLeg。
- 內(nèi)部類(相當(dāng)于 Java 的非靜態(tài)內(nèi)部類)成員可以直接訪問(wèn)外部類的私有數(shù)據(jù),因?yàn)閮?nèi)部類被當(dāng)成其外部類成員,同一個(gè)類的成員之間可以互相訪問(wèn)。但外部類不能訪問(wèn)內(nèi)部類的實(shí)現(xiàn)細(xì)節(jié),例如內(nèi)部類的屬性。
從語(yǔ)法的角度來(lái)看,定義嵌套類(內(nèi)部類)與定義外部類的語(yǔ)法大致相同,只是嵌套類(內(nèi)部類)可使用 protected修飾,用于表示該嵌套類(內(nèi)部類)可在其外部類的子類中被訪問(wèn)。 定義嵌套類(內(nèi)部類)非常簡(jiǎn)單,只要把一個(gè)類放在另一個(gè)類的類內(nèi)部嵌套定義即可。此處的“類內(nèi)部”包括類中的任何位置,甚至在方法中也可以定義嵌套類(方法中定義的嵌套類被稱為局部嵌套類) 。 嵌套類(內(nèi)部類)定義的語(yǔ)法格式如下:
class OuterClass{
//此處可以定義嵌套類、內(nèi)部類
}
內(nèi)部類
內(nèi)部類(等同于Java 的非靜態(tài)內(nèi)部類)相當(dāng)于外部類的實(shí)例成員,因此它可以直接訪問(wèn)外部類的所有成員 。
下面程序在 Cow類中定義了 一個(gè) CowLeg 內(nèi)部類,并在 CowLeg類的方法中直接訪問(wèn) Cow 的 private 屬性。
//通過(guò)主構(gòu)造器為外部類定義屬性
class Cow(var weight: Double = 0.0) {
//定義一個(gè)內(nèi)部類(用 inner 修飾,相當(dāng)于 Java 的非靜態(tài)內(nèi)部類)
//通過(guò)主構(gòu)造器為內(nèi)部類定義屬性
private inner class CowLeg(var length: Double = 0.0, var color: String = "") {
//內(nèi)部類的方法
fun info() {
:("當(dāng)前牛腿顏色是:${color},高:${length}")
//直接訪問(wèn)外部類的 private 修飾的 foo()方法
foo()
}
}
fun test(){
val cl = CowLeg(1.12,"黑白相間")
cl.info()
}
private fun foo() {
println("Cow的foo方法")
}
}
fun main(args: Array<String>) {
val cow = Cow(378.9)
cow.test()
}
上面代碼是一個(gè)普通的類定義,但因?yàn)榘堰@個(gè)類定義放在了另一個(gè)類的內(nèi)部,且使用了inner修飾,所以它就成了一個(gè)內(nèi)部類,可以使用 private修飾符來(lái)修飾這個(gè)類。外部類 Cow 中包含了 一個(gè) test()方法,該方法中創(chuàng)建了一個(gè) CowLeg 對(duì)象,并調(diào)用了該對(duì)象的 info()方法。不難發(fā)現(xiàn),在外部類中使用內(nèi)部類時(shí),與平時(shí)使用普通類并沒(méi)有太大的區(qū)別。
編譯上面程序,將看到在文件所在路徑下生成了兩個(gè) class 文件,其中一個(gè)是 Cow.class, 另 一個(gè)是 Cow$CowLeg.class,前者是外部類 Cow 的 class 文件,后者是內(nèi)部類 CowLeg 的 class 文件,即成員嵌套類和成員內(nèi)部類的 class 文件總是這種形式: OuterClass$InnerClass.class。
前面提到過(guò),在內(nèi)部類中可以直接訪問(wèn)外部類的 private 成員,上面代碼就有在CowLeg類的方法內(nèi)直接訪問(wèn)其外部類的private方法。這是因?yàn)?在內(nèi)部類對(duì)象中 ,保存了一個(gè)它所寄生的外部類對(duì)象的引用 (當(dāng)調(diào)用內(nèi)部類的方法時(shí),必須使用內(nèi)部類對(duì)象作為調(diào)用者,內(nèi)部類實(shí)例必須寄生在外部類實(shí)例中)。
當(dāng)在內(nèi)部類的方法內(nèi)訪問(wèn)某個(gè)屬性時(shí),系統(tǒng)優(yōu)先在該方法內(nèi)查找是否存在該名字的局部變量,如果存在就使用該變量;如果不存在,則到該方法所在的內(nèi)部類中查找是否存在該名字的屬性,如果存在則使用該屬性 : 如果不存在,則到該內(nèi)部類所在的外部類中查找是否存在該名字的屬性,如果存在則使用該屬性:如果依然不存在,系統(tǒng)將出現(xiàn)編譯錯(cuò)誤,提示找不到該屬性。
因此,如果外部類屬性、內(nèi)部類屬性與內(nèi)部類中方法的局部變量同名,則可通過(guò)使用this、 帶標(biāo)簽的 this進(jìn)行限定來(lái)區(qū)分。
class DiscernVariable { //隱式標(biāo)簽@ DiscernVariable
private val prop = "外部類的屬性"
inner class InClass { //隱式標(biāo)簽@ InClass
private val prop = "內(nèi)部類的屬性"
fun info() {
val prop = "局部變量"
//通過(guò)外部類類名.this.varName 訪問(wèn)外部類的屬性
println("外部類的屬性值:${this@DiscernVariable.prop}")
//通過(guò)this.varName 訪問(wèn)內(nèi)部類的屬性
println("內(nèi)部類的屬性值:${this.prop}")
//直接訪問(wèn)局部變盤
println("局部變繭的值:${prop}")
}
}
fun test() {
val ic = InClass()
ic.info()
}
}
fun main(args: Array<String>) {
DiscernVariable().test()
}
上面代碼分別訪問(wèn)外部類的屬性、內(nèi)部類的屬性。通過(guò)帶標(biāo)簽的 this 前綴可顯式指定訪問(wèn)哪個(gè)類的屬性。不帶標(biāo)簽的 this 前綴默認(rèn)訪問(wèn)內(nèi)部類的屬性。
Kotlin的 this,比 Java 的 this 更強(qiáng)大,通過(guò)這種帶標(biāo)簽的 this, Kotlin 可以進(jìn)行非常細(xì)致的區(qū)分。 Kotlin關(guān)于this的處理規(guī)則如下。
- 在類的方法或?qū)傩灾?,this代表調(diào)用該方法或?qū)傩缘膶?duì)象。
- 類的構(gòu)造器中, this代表該構(gòu)造器即將返回的對(duì)象。
- 擴(kuò)展函數(shù)或帶接收者的函數(shù)字面值中,this 表示點(diǎn)(.)左邊的“接收者”。
- 如果 this 沒(méi)有限定符,那么它優(yōu)先代表包含該this的最內(nèi)層的接收者,并且會(huì)自動(dòng)向外搜索。如果要讓 this 明確引用特定的接收者,則可使用標(biāo)簽限定符。
如下程序示范了一個(gè)比較復(fù)雜的this示例,讀者可以參考該程序和注釋來(lái)理解Kotlin對(duì)this 的處理 。
class A { //隱式標(biāo)簽@ A
inner class B { //隱式標(biāo)簽@B
//為 Int 擴(kuò)展 foo ()方法
fun Int.foo() { //隱式標(biāo)簽@ foo
val a = this@A //A 的 this
val b = this@B //B 的 this
val c = this //不帶標(biāo)簽的 this,默認(rèn)代表該方法所屬對(duì)象: Int對(duì)象
val c1 = this@foo //顯式指定@foo標(biāo)簽,與c代表的對(duì)象相同
println(a)
println(b)
println(c)
println(c1)
//為String擴(kuò)展funLit()方法
val funLit = lambda@ fun String.() {
val d = this //不帶標(biāo)簽的 this,默認(rèn)代表該方法所屬對(duì)象: String對(duì)象
val d1 = this@lambda //顯式指定@lambda標(biāo)簽,與 d代表的對(duì)象相同
println(d)
println(d1)
}
"fkit".funLit()
//直接定義一個(gè) Lambda 表達(dá)式,沒(méi)有接收者
val funLit2 = {
//該 this 所在的 Lambda 表達(dá)式?jīng)]有接收者 ,因此當(dāng)前范圍沒(méi)有 this
//系統(tǒng)會(huì)繼續(xù)向該 Lambda 表達(dá)式所在范圍搜索 this
//故此處 this 將代表 foo ()方法的接收者: Int 對(duì)象
val e = this
val e1 = this@foo ////顯式指定@foo標(biāo)簽,與 e 代表的對(duì)象相同
println("foo ()方法中 Lambda 表達(dá)式的 this: ${e}")
println("e1的this:${e1}")
}
funLit2()
}
fun testB() {
//調(diào)用2 (Int值)的foo()方法
2.foo()
}
}
fun testA() {
var bObj = B()
println("程序創(chuàng)建的B對(duì)象: ${bObj}")
bObj.testB()
}
}
fun main(args: Array<String>) {
var aObj =A()
println("程序創(chuàng)建的A對(duì)象: ${aObj}")
aObj.testA()
}
內(nèi)部類的成員可以訪問(wèn)外部類的 private 成員,但反過(guò)來(lái)就不成立了。內(nèi)部類的成員只在內(nèi)部類范圍內(nèi)是可知的,并不能被外部類直接使用。如果外部類需要訪問(wèn)內(nèi)部類的成員,則必須顯式創(chuàng)建內(nèi)部類對(duì)象來(lái)調(diào)用訪問(wèn)其成員。
嵌套類
嵌套類相當(dāng)于 Java 的靜態(tài)內(nèi)部類,因此嵌套類直接屬于外部類的類本身,而不是外部類實(shí)例相關(guān) 。
Java語(yǔ)法有一條規(guī)則:靜態(tài)成員不可訪問(wèn)非靜態(tài)成員。而 Kotlin徹底取消了static修飾符,因此Kotlin 類中的成員除嵌套類之外,全部都是非靜態(tài)成員,因此嵌套類不可訪問(wèn)外部類的其他任何成員(只能訪問(wèn)其他嵌套類)。
class NestedClassTest {
var prop1 = 5
fun test() {
println("外部類的test()")
}
//沒(méi)有 inner 修飾符,是嵌套類(相當(dāng)于 Java 的靜態(tài)內(nèi)部類)
class NestedClass {
fun accessOuterMember() {
//訪問(wèn)另一個(gè)嵌套類是允許的
val a = A()
//下面兩行代碼都會(huì)出現(xiàn)錯(cuò)誤
//println(prop1)
//test()
}
}
class A
}
上面代碼定義了一個(gè)屬性和一個(gè)方法,NestedClass類中定義了 一個(gè)accessOuterMember()方法,但它不能訪問(wèn)外部類的 propl 屬性和 test()方法。 嵌套類唯一可訪問(wèn)的是外部類的其他嵌套類。 嵌套類相當(dāng)于外部類的靜態(tài)成員,因此外部類的所有方法、屬性、初始化塊都可以使用嵌套類來(lái)定義變量 、創(chuàng)建對(duì)象等。 外部類依然不能直接訪問(wèn)嵌套類的成員,但可以使用嵌套類的對(duì)象作為調(diào)用者來(lái)訪問(wèn)嵌套類的成員。
除此之外, Kotlin 還允許在接口中定義嵌套類,但不允許在接口中定義內(nèi)部類(即不允許定義使用 inner 修飾的內(nèi)部類)。如果為接口中的嵌套類指定訪問(wèn)控制符,則只能指定 public 或 private; 如果定義接口中的嵌套類時(shí)省略訪問(wèn)控制符,則該嵌套類默認(rèn)是 public 訪問(wèn)權(quán)限。
在外部類以外使用內(nèi)部類
定義類的主要作用就是定義變量、創(chuàng)建對(duì)象和派生子類。定義內(nèi)部類的主要作用也如此。正如前面所看到的,在外部類內(nèi)部使用嵌套類或內(nèi)部類時(shí),與平常使用普通類沒(méi)有太大的區(qū)別,一樣可以直接通過(guò)嵌套類或內(nèi)部類的類名來(lái)定義變量,調(diào)用嵌套類或內(nèi)部類的構(gòu)造器來(lái)創(chuàng)建實(shí)例。
唯一要牢記的一點(diǎn):嵌套類只能訪問(wèn)外部的其他嵌套類,不能訪問(wèn)外部的其他任何成員。
如果希望在外部類以外的地方使用內(nèi)部類或嵌套類,一定要注意訪問(wèn)權(quán)限的限制,比如使用 private修飾的嵌套類或內(nèi)部類只能在外部類之內(nèi)使用。
在外部類以外的地方定義內(nèi)部類變量的語(yǔ)法格式如下
var | val varName: OuterClass.InnerClass
從上面語(yǔ)法格式可以看出,在外部類以外的地方使用內(nèi)部類時(shí),內(nèi)部類完整的類名應(yīng)該是
OuterClass.InnerClass。如果外部類有包名,則還應(yīng)該增加包名前綴。
由于內(nèi)部類的對(duì)象必須寄生在外部類的對(duì)象中,因此在創(chuàng)建內(nèi)部類對(duì)象之前,必須先創(chuàng)建
其外部類對(duì)象。 在外部類以外的地方創(chuàng)建內(nèi)部類實(shí)例的語(yǔ)法格式如下
Outerinstance.InnerConstructor()
從上面語(yǔ)法格式可以看出,在外部類以外的地方創(chuàng)建內(nèi)部類實(shí)例時(shí)必須使用外部類實(shí)例來(lái)調(diào)用內(nèi)部類的構(gòu)造器。下面程序示范了如何在外部類以外的地方創(chuàng)建內(nèi)部類對(duì)象,并把它賦值給內(nèi)部類類型的變量。
class Out {
//定義一個(gè)內(nèi)部類,不使用訪問(wèn)控制符,默認(rèn)是 public
inner class In(msg: String) {
init {
println(msg)
}
}
}
fun main(args: Array<String>) {
var oi :Out.In = Out().In("測(cè)試信息")
}
從上面代碼可以看出,內(nèi)部類的構(gòu)造器必須使用外部類對(duì)象來(lái)調(diào)用。
在外部類以外使用嵌套類
因?yàn)榍短最愂菍儆谕獠款惖念惐旧淼?,因此?chuàng)建嵌套類對(duì)象時(shí)無(wú)須創(chuàng)建外部類對(duì)象,所以嵌套類用起來(lái)非常方便。在外部類以外的地方創(chuàng)建嵌套類實(shí)例的語(yǔ)法格式如下:
OuterClass. NestedConstructor()
class NestedOut {
//定義一個(gè)嵌套類 , 不使用訪問(wèn)控制符,默認(rèn)是 public
open class Nested {
init {
println("嵌套類的構(gòu)造器")
}
}
}
fun main(args: Array<String>) {
var nn :NestedOut.Nested = NestedOut.Nested()
}
從上面代碼可以看出,不管是內(nèi)部類還是嵌套類,其聲明變量的語(yǔ)法完全一樣。區(qū)別只是在創(chuàng)建對(duì)象時(shí),嵌套類只需使用外部類即可調(diào)用構(gòu)造器,而內(nèi)部類必須使用外部類對(duì)象來(lái)調(diào)用構(gòu)造器。
因?yàn)樵谡{(diào)用嵌套類的構(gòu)造器時(shí)無(wú)須使用外部類對(duì)象,所以創(chuàng)建嵌套類的子類也比較簡(jiǎn)單。 下面代碼就為嵌套類 NestedSubClass 定義了一個(gè)空的子類 。
class NestedSubClass :NestedOut.Nested()
相比之下,使用嵌套類比使用內(nèi)部類要簡(jiǎn)單很多,只要把外部類當(dāng)作嵌套類的包空間即可。 因此程序應(yīng)該優(yōu)先考慮使用嵌套類。
局部嵌套類
如果把一個(gè)嵌套類放在方法或函數(shù)中定義,則這個(gè)嵌套類就是一個(gè)局部嵌套類,局部嵌套類僅在該方法或函數(shù)中有效。由于局部嵌套類不能在方法或函數(shù)以外的地方使用,因此局部嵌套類也不能使用訪問(wèn)控制符修飾。
如果需要用局部嵌套類定義變量、創(chuàng)建實(shí)例或派生子類,那么都只能在局部嵌套類所在的方法(或函數(shù))內(nèi)進(jìn)行。
class LocalNestedClass {
fun info() {
//定義局部嵌套類
open class NestedBase(var a: Int = 0) {
}
//定義用部嵌套類的子類
class NestedSub(var b: Int = 0) : NestedBase() {
}
//創(chuàng)建局部嵌套類的對(duì)象
val ns = NestedSub()
ns.a = 5
ns.b = 8
println("NestedSub對(duì)象的 a和 b屬性是:${ns.a},${ns.b}")
}
}
fun main(args: Array<String>) {
LocalNestedClass().info()
}
編譯上面程序,可以看到生成了三 class 文件:LocalNestedClass.class、 LocalNestedClass$1NestedBase.class和 LocalNestedClass$1NestedSub.class,這表明局部嵌套類的class文件總是遵循如下命名格式: OuterClass$NNestedClass.class。注意到局部嵌套類的 class 文件的文件名比嵌套類、內(nèi)部類的class文件的文件名多了一個(gè)數(shù)字,這是因?yàn)橥粋€(gè)類中不可能有兩個(gè)同名的嵌套類、內(nèi)部類,而同一個(gè)類中則可能有兩個(gè)以上同名的局部嵌套類(處于不同的方法中), 所以 Kotlin 為局部嵌套類的 class 文件名增加了一個(gè)數(shù)字,用于區(qū)分。
局部嵌套類是一個(gè)非?!半u肋”的語(yǔ)法,在實(shí)際開(kāi)發(fā)中很少定義局部嵌套類,這是因?yàn)榫植壳短最惖淖饔糜蛱×?,只能在?dāng)前方法中使用。
匿名內(nèi)部類
Java 有一個(gè)非常實(shí)用的功能:匿名內(nèi)部類, Kotlin 則徹底拋棄了這個(gè)功能。不過(guò)讀者不用擔(dān)心, Kotlin 提供了一個(gè)更加強(qiáng)大的語(yǔ)法 : 對(duì)象表達(dá)式。
此外,如果對(duì)象是函數(shù)式接口(只包含一個(gè)抽象方法的接口)的實(shí)例,則可使用帶接口類型前綴的 Lambda 表達(dá)式創(chuàng)建它。 例如,如下代碼創(chuàng)建了一個(gè) Runnable 實(shí)例來(lái)啟動(dòng)線程。
fun main(args: Array<String>) {
//使用 Lambda 表達(dá)式創(chuàng)建 Runnable 實(shí)例
var t = Runnable {
for (i in 1..100) {
println("${Thread.currentThread().getName()} ,i: ${i}")
}
}
//啟動(dòng)新線程
Thread(t).start()
//主線程的循環(huán)
for (i in 1..100) {
println("${Thread.currentThread().getName()},i: ${i}")
}
}
上面程序中代碼使用 Lambda 表達(dá)式創(chuàng)建了一個(gè) Runnable 實(shí)例,通過(guò) Runnable 實(shí)例可啟動(dòng)多個(gè)線程 。
對(duì)象表達(dá)式其實(shí)就是增強(qiáng)版的匿名內(nèi)部類,下面會(huì)詳細(xì)介紹。
對(duì)象表達(dá)式和對(duì)象聲明
Kotlin 提供了比匿名內(nèi)部類更加強(qiáng)大的語(yǔ)法:對(duì)象表達(dá)式。它們的主要區(qū)別在于:匿名內(nèi)部類只能指定一個(gè)父類型(接口或父類),但對(duì)象表達(dá)式可指定 0~N個(gè)父類型(接口或父類)。
對(duì)象表達(dá)式
對(duì)象表達(dá)式的語(yǔ)法格式如下:
object[: 0~N 個(gè)父類型] {
//對(duì)象表達(dá)式的類體部分
}
從上面語(yǔ)法格式可以看出,對(duì)象表達(dá)式可以指定0~N個(gè)父類型(類或接口),對(duì)象表達(dá)式的本質(zhì)就是增強(qiáng)版的匿名內(nèi)部類,因此編譯器處理對(duì)象表達(dá)式時(shí)也會(huì)像匿名內(nèi)部類一樣生成對(duì)應(yīng)的class文件。
關(guān)于對(duì)象表達(dá)式還有如下規(guī)則。
- 對(duì)象表達(dá)式不能是抽象類,因?yàn)橄到y(tǒng)在創(chuàng)建對(duì)象表達(dá)式時(shí)會(huì)立即創(chuàng)建對(duì)象。因此不允許將對(duì)象表達(dá)式定義成抽象類。
- 對(duì)象表達(dá)式不能定義構(gòu)造器。但對(duì)象表達(dá)式可以定義初始化塊,可以通過(guò)初始化塊來(lái)完成構(gòu)造器需要完成的事情。
- 對(duì)象表達(dá)式可以包含內(nèi)部類(有 inner修飾的內(nèi)部類),不能包含嵌套類。
下面程序示范了幾種對(duì)象表達(dá)式。
interface Outputable {
fun output(msg: String)
}
abstract class Product(var price: Double) {
abstract val name: String
abstract fun printinfo()
}
fun main(args: Array<String>) {
//指定一個(gè)父類型(接口)的對(duì)象表達(dá)式
var ob1 = object : Outputable {
override fun output(msg: String) {
//重寫父接口中的抽象方法
for (i in 1..6) {
println("<h${i}>${msg}</h${i}>")
}
}
}
ob1.output("java")
println("------------")
//指定0個(gè)父類型的對(duì)象表達(dá)式
var ob2 = object {
//初始化塊
init {
println("初始化塊")
}
//屬性
var name = "kotlin"
//方法
fun test(){
println("test()方法")
}
//只能包含內(nèi)部類,不能包含嵌套類
inner class Foo{}
}
println(ob2.name)
ob2.test()
println("------------")
//指定兩個(gè)父類型的對(duì)象表達(dá)式
//由于 Product 只有一個(gè)帶參數(shù)的構(gòu)造器,因此需要傳入構(gòu)造器參數(shù)
var ob3 = object :Outputable,Product(28.8){
override fun output(msg: String) {
println("輸出信息:${msg}")
}
override val name: String
get() = "激光打印機(jī)"
override fun printinfo() {
println("”高速激光打印機(jī),支持自動(dòng)雙面打印!")
}
}
println(ob3.name)
ob3.output("Kotlin 真不錯(cuò)!")
ob3.printinfo()
}
上面代碼的第一個(gè)對(duì)象表達(dá)式只有一個(gè)父類型(接口),這個(gè)對(duì)象表達(dá)式與Java 的匿名內(nèi)部類實(shí)現(xiàn)太像了,除了 Kotlin 的對(duì)象表達(dá)式需要使用 object 關(guān)鍵字,該對(duì)象表達(dá)式定義的匿名類實(shí)現(xiàn)了 Outputable接口,因此在類體部分實(shí)現(xiàn)了該接口中的抽象方法。
從上面介紹可以看出, Kotlin 的對(duì)象表達(dá)式可以完全取代 Java 的匿名內(nèi)部類 。
上面代碼的第二個(gè)對(duì)象表達(dá)式則沒(méi)有指定父類型,這就是對(duì)象表達(dá)式的增強(qiáng)之處。我們?cè)谠搶?duì)象表達(dá)式的類體部分定義了初始化塊(程序創(chuàng)建對(duì)象時(shí)會(huì)自動(dòng)執(zhí)行)、屬性、方法、內(nèi)部類,由此可見(jiàn),對(duì)象表達(dá)式的本質(zhì)其實(shí)就是增強(qiáng)版的匿名內(nèi)部類,因此在類體部 除不能定義構(gòu)造器、嵌套類之外,完全可以定義其他成員。
上面代碼的第三個(gè)對(duì)象表達(dá)式指定了兩個(gè)父類型,一個(gè)是接口,一個(gè)是抽象類,這也是對(duì)象表達(dá)式的增強(qiáng)之處。此處需要說(shuō)明的是,就像 Java 的匿名內(nèi)部類繼承抽象類時(shí),必須調(diào)用抽象類的指定構(gòu)造器一樣, Kotlin也是如此,所以我們使用了Product(28.8),表 明調(diào)用Product 抽象類的帶一個(gè) Double 參數(shù)的構(gòu)造器 。該對(duì)象表達(dá)式的類體部分沒(méi)有任何特別之處,它只是重寫了接口、抽象父類的抽象成員。
運(yùn)行上面代碼,仔細(xì)查看輸出,應(yīng)該會(huì)發(fā)現(xiàn)一個(gè)特征: ob2 對(duì)象沒(méi)有繼承任何父類型,程序?yàn)樵搶?duì)象(相當(dāng)于匿名內(nèi)部類)定義了 name 屬性和 test()方法,接下來(lái)程序居然可以直接調(diào)用該對(duì)象的 name 屬性和 test()方法一一這對(duì) Java 的匿名內(nèi)部類來(lái)說(shuō)是不可想象的 : Java 為匿名內(nèi)部類增加的方法幾乎無(wú)法直接訪問(wèn),因?yàn)榫幾g器只會(huì)把匿名內(nèi)部類當(dāng)成它所繼承的父類或所實(shí)現(xiàn)的接口處理。
但 Kotlin 的對(duì)象表達(dá)式不同, Kotlin 的對(duì)象表達(dá)式可分為兩種情形 :
- 對(duì)象表達(dá)式在方法(或函數(shù))的局部范圍內(nèi),或使用 private修飾的對(duì)象表達(dá)式, Kotlin編譯器可識(shí)別該對(duì)象表達(dá)式的真實(shí)類型,就像上面的代碼所示:程序?yàn)?ob2 增加了方法和屬性,在 main()函數(shù)的局部范圍內(nèi), Kotlin編譯器完全可以識(shí)別ob2的真實(shí)類型, 因此 ob2可調(diào)用對(duì)象表達(dá)式增加的屬性和方法。
- 非private修飾的對(duì)象表達(dá)式與Java的匿名內(nèi)部類相似,編譯器只會(huì)把對(duì)象表達(dá)式當(dāng)成它所繼承的父類或所實(shí)現(xiàn)的接口處理。如果它沒(méi)有父類型,系統(tǒng)當(dāng)它是 Any 類型。
如下程序示范了 Kotlin編譯器處理對(duì)象表達(dá)式類型的兩種情形:
class ObjectExprType {
private val ob1 = object {
val name: String = "java"
}
internal val ob2 = object {
val name: String = "java"
}
private fun privateBar() = object {
val name: String = "kotlin"
}
fun publicBar() = object {
val name: String = "kotlin"
}
fun test(){
//ob1是private對(duì)象表達(dá)式,編譯器可識(shí)別它的真實(shí)類型
//下面代碼正確
println(ob1.name)
//ob2是非private對(duì)象表達(dá)式,編譯器當(dāng)它是Any類型
//下面代碼錯(cuò)誤
//println(ob2.name)
//privateBar private 函數(shù),編譯器可識(shí)別它返回的對(duì)象表達(dá)式的真實(shí)類型
//下面代碼正確
println(privateBar().name)
//publicBar是非private函數(shù),編譯器將它返回的對(duì)象表達(dá)式當(dāng)成Any類型
//下面代碼錯(cuò)誤
//println(publicBar().name)
}
}
fun main(args: Array<String>) {
ObjectExprType().test()
}
從上面代碼可以看出, Kotlin 編譯器可識(shí)別 private 對(duì)象表達(dá)式的真實(shí)類型。
此外, Kotlin 的對(duì)象表達(dá)式可訪問(wèn)或修改其作用域內(nèi)的局部變量(java只能訪問(wèn)其所在范圍內(nèi)的 effectively final局部變量)。
例如如下程序 :
fun main(args: Array<String>) {
var a = 10
var obj = object {
fun change(){
a++
}
}
obj.change()
println("a的值:${a}")
}
總結(jié)起來(lái), Kotlin的對(duì)象表達(dá)式比 Java的匿名內(nèi)部類增強(qiáng)了三個(gè)方面。
- 對(duì)象表達(dá)式可指定多個(gè)父類型。
- Kotlin編譯器能更準(zhǔn)確地識(shí)別局部范圍內(nèi)或 private對(duì)象表達(dá)式的類型。
- 對(duì)象表達(dá)式可訪問(wèn)或修改其所在范圍內(nèi)的局部變量。
對(duì)象聲明和單例模式
對(duì)象聲明的語(yǔ)法格式如下:
object ObjectName[: 0~N個(gè)父類型]{
//對(duì)象聲明的類體部分
}
從上面語(yǔ)法格式可以看出,對(duì)象聲明與對(duì)象表達(dá)式的語(yǔ)法非常相似,似乎它們之間唯一的區(qū)別是對(duì)象表達(dá)式在object關(guān)鍵字后沒(méi)有名字 ;而對(duì)象聲明需要在 object關(guān)鍵字后指定名字。
實(shí)際上,對(duì)象聲明和對(duì)象表達(dá)式還存在如下區(qū)別:
- 對(duì)象表達(dá)式是一個(gè)表達(dá)式,因此它可以被賦值給變量 ; 而對(duì)象聲明不是表達(dá)式,因此它不能用于賦值。
- 對(duì)象聲明可包含嵌套類,不能包含內(nèi)部類 ; 而對(duì)象表達(dá)式可包含內(nèi)部類,不能包含嵌套類。
- 對(duì)象聲明不能定義在函數(shù)和方法內(nèi) ; 但對(duì)象表達(dá)式可嵌套在其他對(duì)象聲明或非內(nèi)部類中。
下面我們用對(duì)象聲明來(lái)改寫上面的代碼:
interface Outputable {
fun output(msg: String)
}
abstract class Product(var price: Double) {
abstract val name: String
abstract fun printinfo()
}
//指定一個(gè)父類型(接口)的對(duì)象聲明
object MyObject1 : Outputable {
override fun output(msg: String) {
//重寫父接口中的抽象方法
for (i in 1..6) {
println("<h${i}>${msg}</h${i}>")
}
}
}
//指定0個(gè)父類型的對(duì)象聲明
object MyObject2 {
//初始化塊
init {
println("初始化塊")
}
//屬性
var name = "kotlin"
//方法
fun test() {
println("test()方法")
}
//只能包含嵌套類,不能包含內(nèi)部類
class Foo
}
//指定兩個(gè)父類型的對(duì)象聲明
//由于 Product 只有一個(gè)帶參數(shù)的構(gòu)造器,因此需要傳入構(gòu)造器參數(shù)
object MyObject3 : Outputable, Product(28.8) {
override fun output(msg: String) {
println("輸出信息:${msg}")
}
override val name: String
get() = "激光打印機(jī)"
override fun printinfo() {
println("”高速激光打印機(jī),支持自動(dòng)雙面打印!")
}
}
fun main(args: Array<String>) {
MyObject1.output("java")
println("------------")
println(MyObject2.name)
MyObject2.test()
println("------------")
println(MyObject3.name)
MyObject3.output("Kotlin 真不錯(cuò)!")
MyObject3.printinfo()
}
將上面程序與上面的對(duì)象表達(dá)式的第一個(gè)程序進(jìn)行對(duì)比,即可發(fā)現(xiàn)對(duì)象聲明不能用于賦值,對(duì)象聲明本身己有名稱,因此可以使用對(duì)象聲明的名稱訪問(wèn)該對(duì)象。此外,程序還將對(duì)象聲明移到 main()函數(shù)之外,這是因?yàn)閷?duì)象聲明不允許放在函數(shù)或方法內(nèi)。
對(duì)象聲明專門用于實(shí)現(xiàn)單例模式,對(duì)象聲明所定義的對(duì)象也就是該類的唯一實(shí)例,程序可通過(guò)對(duì)象聲明的名稱直接訪問(wèn)該類的唯一實(shí)例。
伴生對(duì)象和靜態(tài)成員
在類中定義的對(duì)象聲明,可使用 companion修飾,這樣該對(duì)象就變成了伴生對(duì)象。
每個(gè)類最多只能定義一個(gè)伴生對(duì)象,伴生對(duì)象相當(dāng)于外部類的對(duì)象,程序可通過(guò)外部類直接調(diào)用伴生對(duì)象的成員。例如如下代碼:
interface Outputable {
fun output(msg: String)
}
class MyClass {
//使用 companion 修飾的伴生對(duì)象
companion object MyObject1 : Outputable {
val name = "name屬性值"
override fun output(msg: String) {
for (i in 1..6) {
println("<h${i}>${msg}</h${i}>")
}
}
}
}
fun main(args: Array<String>) {
//使用伴生對(duì)象所在的類調(diào)用伴生對(duì)象的方法
MyClass.output("kotlin")
println(MyClass.name)
}
上面代碼使用 companion修飾了對(duì)象聲明,因此該對(duì)象變成了伴生對(duì)象。接下來(lái)在主程序中即可使用伴生對(duì)象所在的類調(diào)用伴生對(duì)象的成員。
從上面的 MyClass.output("kotlin")代碼不難發(fā)現(xiàn): 這不就是 Java使用類訪問(wèn)靜態(tài)成員的語(yǔ)法嗎? 實(shí)際上確實(shí)如此,由于Kotlin取消了static關(guān)鍵字,因此Kotlin引入伴生對(duì)象來(lái)彌補(bǔ)沒(méi)有靜態(tài)成員的不足。可見(jiàn),伴生對(duì)象的主要作用就是為其所在的外部類模擬靜態(tài)成員。
從代碼可以看出,伴生對(duì)象的名稱并不重要,因此伴生對(duì)象可以省略名稱。省略名稱之后,如果程序真的要訪問(wèn)伴生對(duì)象,則可通過(guò) Companion 名稱進(jìn)行訪問(wèn)。例如如下代碼。
interface Outputable {
fun output(msg: String)
}
class MyClass {
//省略名字的伴生對(duì)象
companion object : Outputable {
val name = "name屬性值"
override fun output(msg: String) {
for (i in 1..6) {
println("<h${i}>${msg}</h${i}>")
}
}
}
}
fun main(args: Array<String>) {
//使用伴生對(duì)象所在的類調(diào)用伴生對(duì)象的方法
MyClass.output("kotlin")
//使用 Companion 名稱訪問(wèn)伴生對(duì)象
println(MyClass.Companion)
}
雖然伴生對(duì)象的主要作用就是為它所在的類模擬靜態(tài)成員,但只是模擬,伴生對(duì)象的成員依然是伴生對(duì)象本身的實(shí)例成員,并不屬于伴生對(duì)象所在的外部類。
在JVM平臺(tái)上,可通過(guò)@JvmStatic注解讓系統(tǒng)根據(jù)伴生對(duì)象的成員為其所在的外部類生成真正的靜態(tài)成員。具體信息可參考后面的Kotlin與Java互相調(diào)用。
伴生對(duì)象的擴(kuò)展
伴生對(duì)象也可以被擴(kuò)展。如果一個(gè)類具有伴生對(duì)象,則Kotlin允許為伴生對(duì)象擴(kuò)展方法和屬性。為伴生對(duì)象擴(kuò)展的方法和屬性,就相當(dāng)于為伴生對(duì)象所在的外部類擴(kuò)展了靜態(tài)成員,可通過(guò)外部類的類名訪問(wèn)這些擴(kuò)展成員。
如下程序示范了為伴生對(duì)象擴(kuò)展成員:
interface Outputable {
fun output(msg: String)
}
class MyClass {
//省略名字的伴生對(duì)象
companion object : Outputable {
val name = "name屬性值"
override fun output(msg: String) {
for (i in 1..6) {
println("<h${i}>${msg}</h${i}>")
}
}
}
}
//為伴生對(duì)象擴(kuò)展方法
fun MyClass.Companion.test(){
println("為伴生對(duì)象擴(kuò)展的方法")
}
val MyClass.Companion.foo
get() = "為伴生對(duì)象擴(kuò)展的屬性"
fun main(args: Array<String>) {
//使用伴生對(duì)象所在的類調(diào)用伴生對(duì)象的方法
MyClass.output("kotlin")
//使用 Companion 名稱訪問(wèn)伴生對(duì)象
println(MyClass.Companion)
//通過(guò)伴生對(duì)象所在的類調(diào)用為伴生對(duì)象擴(kuò)展的成員
MyClass.test()
println(MyClass.foo)
}
上面代碼示范了為伴生對(duì)象擴(kuò)展方法和屬性,正如前面所提到的,Kotlin 可通過(guò)Companion名稱訪問(wèn)伴生對(duì)象,因此代碼顯式指定為 MyClass.Companion 擴(kuò)展方法和屬性,這就是為伴生對(duì)象擴(kuò)展方法和屬性。
枚舉類
Kotlin 當(dāng)然也支持枚舉類, Kotlin 的枚舉類與 Java 差別不大。
枚舉類入門
Kotlin使用 enum class關(guān)鍵字組合定義枚舉類。枚舉類是一種特殊的類,它一樣可以有自己的屬性、方法,可以實(shí)現(xiàn)一個(gè)或多個(gè)接口,也可以定義自己的構(gòu)造器。
但枚舉類與普通類有如下簡(jiǎn)單區(qū)別:
- 枚舉類可以實(shí)現(xiàn)一個(gè)或多個(gè)接口,使用enum定義的枚舉類默認(rèn)繼承 kotlin.Enum 類,而不是默認(rèn)繼承Any類,因此枚舉類不能顯式繼承其他父類。其中Enum類實(shí)現(xiàn)了kotlin.Comparable 接口 。
- 使用 enum 定義的非抽象的枚舉類不能使用 open修飾,因此枚舉類不能派生子類。
- 枚舉類的構(gòu)造器只能使用private訪問(wèn)控制符,如果省略了構(gòu)造器的訪問(wèn)控制符,則默認(rèn)使用 private 修飾;如果強(qiáng)制指定訪問(wèn)控制符,則只能指定 private 修飾符。
- 枚舉類的所有實(shí)例必須在枚舉類的第一行顯式列出,否則這個(gè)枚舉類永遠(yuǎn)都不能產(chǎn)生實(shí)例。列出枚舉實(shí)例后最好用分號(hào)結(jié)尾。
枚舉類默認(rèn)提供了如下兩個(gè)方法 。
- EnumClass.valueOf(value: String): EnumClass:類似于Java 枚舉類的 valueOf()方法,用于根據(jù)枚舉的字符串名獲取實(shí)際的枚舉值。如果傳入的名稱參數(shù)與類中定義的任何枚舉常量均不匹配, valueOf()方法將拋出 IllegalArgumentException 異常。
- EnumClass.values(): Array<EnumClass>:類似于 Java 枚舉類的 values()方法,用于獲取該枚舉的所有枚舉值組成的數(shù)組。
下面程序定義了一個(gè) Season 枚舉類
enum class Season{
//在第一行列出 4 個(gè)枚舉實(shí)例
SPRING, SUMMER, FALL , WINTER
}
編譯上面的Kotlin 程序,將生成一個(gè)Season.class 文件,這表明枚舉類是一種特殊的類 。 由此可見(jiàn), enum class 關(guān)鍵字組合和 class、 interface 關(guān)鍵字的作用大致相似 。
在定義枚舉類時(shí),需要顯式列出所有的枚舉值,如上面的 SPRING, SUMMER, FALL, WINTER 所示,所有的枚舉值之間以英文逗號(hào)(,)隔開(kāi)。這些枚舉值代表了該枚舉類的所有可能的實(shí)例。
如果需要使用該枚舉類的某個(gè)實(shí)例,則可使用 EnumClass.variable 的形式,如Season.SPRING 。
enum class Season{
//在第一行列出 4 個(gè)枚舉實(shí)例
SPRING, SUMMER, FALL , WINTER
}
fun main(args: Array<String>) {
//枚舉類默認(rèn)有一個(gè) values ()方法,返回該枚舉類的所有實(shí)例
for (s in Season.values()){
println(s)
}
val seasonName = "SUMMER"
val s = Season.valueOf(seasonName)
println(s)
//直接訪問(wèn)枚舉值
println(Season.WINTER)
}
上面程序測(cè)試了 Season 枚舉類的用法,該類通過(guò) values()方法返回了 Season 枚舉類的所有實(shí)例,并通過(guò)循環(huán)迭代輸出了 Season 枚舉類的所有實(shí)例;還使用了 Season 的 valueOf()方法根據(jù)字符串參數(shù)來(lái)獲取枚舉值。
前面己經(jīng)介紹過(guò),所有的枚舉類都繼承了 kotlin.Enum類,所以枚舉類可以直接使用該類中所包含的屬性和方法。 kotlin.Enum 類中提供了如下屬性和方法。
- name 屬性: 返回此枚舉實(shí)例的名稱,這個(gè)名稱就是定義枚舉類時(shí)列出的所有枚舉值之一。與此屬性相比,應(yīng)該優(yōu)先考慮使用 toString()方法,因?yàn)?toString() 方法可返回對(duì)用戶更加友好的名稱。
- ordinal 屬性: 返回枚舉值在枚舉類中的索引值(就是枚舉值在枚舉聲明中的位置,第一個(gè)枚舉值的索引值為 0) 。
- int compareTo(E o): 該方法用于與指定的枚舉對(duì)象比較順序,同 一個(gè)枚舉實(shí)例只能與相同類型的枚舉實(shí)例進(jìn)行比較。如果該枚舉對(duì)象位于指定的枚舉對(duì)象之后,則返回正整數(shù);如果該枚舉對(duì)象位于指定的枚舉對(duì)象之前,則返回負(fù)整數(shù) ; 否則返回 0。
- String toString(): 返回枚舉常量的名稱,與 name 屬性相似,但 toString()方法更常用。
正如前面所看到的,當(dāng)程序使用 println(s)語(yǔ)句來(lái)打印枚舉值時(shí),實(shí)際上輸出的是該枚舉值toString()方法的返回值,也就是輸出該枚舉值的名字。
枚舉類的屬性、方法和構(gòu)造器
枚舉類也是一種類,只是一種比較特殊的類,因此它一樣可以定義屬性、方法和構(gòu)造器。由于枚舉類應(yīng)該設(shè)計(jì)成不可變類,因此它的屬性值不允許改變,這樣會(huì)更安全。 Kotlin禁止開(kāi)發(fā)者對(duì)屬性賦值,并推薦使用 val 為枚舉聲明只讀屬性。 由于枚舉的屬性都是只讀屬性,枚舉必須在構(gòu)造器中為這些屬性指定初始值(或在初始化塊中指定初始值, 一般不會(huì)在定義時(shí)指定初始值,因?yàn)檫@樣會(huì)導(dǎo)致所有枚舉值的該屬性值總是相同的),因此應(yīng)該為枚舉類顯式定義帶參數(shù)的構(gòu)造器。
一旦為枚舉類顯式定義了帶參數(shù)的構(gòu)造器,在列出枚舉值時(shí)就必須對(duì)應(yīng)地傳入?yún)?shù)。例如如下程序。
//使用主構(gòu)造器聲明 cnName 只讀屬性
enum class Gender(val cnName: String) {
MALE("男"), FEMALE("女");
//定義方法
fun info() {
when (this) {
MALE -> println("男的")
FEMALE -> println("女的")
}
}
}
從上面程序中可以看出,當(dāng)為 Gender枚舉類定義了一個(gè) Gender(String)構(gòu)造器之后,列出枚舉值時(shí)就必須對(duì)應(yīng)地傳入?yún)?shù)。也就是說(shuō),在枚舉類中列出枚舉值時(shí),實(shí)際上就是調(diào)用構(gòu)造器創(chuàng)建枚舉類對(duì)象,只是這里無(wú)須顯式調(diào)用構(gòu)造器 。前面列出枚舉值時(shí)無(wú)須傳入?yún)?shù),甚至無(wú)須使用括號(hào),僅僅是因?yàn)榍懊娴拿杜e類包含無(wú)參數(shù)的構(gòu)造器。
不難看出,上面程序中的粗體字代碼實(shí)際上等同于如下代碼:
MALE = new Gender ("男" ) , FEMALE = new Gender ("女");
此外,由于上面的枚舉類需要在列出枚舉值之后定義額外的枚舉成員(如枚舉方法),因此上面程序需要用分號(hào)表示枚舉值列表結(jié)束 。
下面程序示范了 Gender枚舉類的用法。
//使用主構(gòu)造器聲明 cnName 只讀屬性
enum class Gender(val cnName: String) {
MALE("男"), FEMALE("女");
//定義方法
fun info() {
when (this) {
MALE -> println("男的")
FEMALE -> println("女的")
}
}
}
fun main(args: Array<String>) {
//通過(guò) Gender 的 valueOf ()方法根據(jù)枚舉名獲取枚舉值
val g = Gender.valueOf("FEMALE")
//訪問(wèn)枚舉值的 enName 屬性
println("${g}代表:${g.cnName}")
//調(diào)用 info 方法
g.info()
}
實(shí)現(xiàn)接口的枚舉類
枚舉類也可以實(shí)現(xiàn)一個(gè)或多個(gè)接口 。 與普通類實(shí)現(xiàn)一個(gè)或多個(gè)接口完全一樣,枚舉類實(shí)現(xiàn)一個(gè)或多個(gè)接口時(shí),也需要實(shí)現(xiàn)該接口所包含的方法 。下面程序定義了一個(gè) GenderDesc 接口。
interface GenderDesc{
fun info()
}
//使用主構(gòu)造器聲明 cnName 只讀屬性
enum class Gender(val cnName: String) :GenderDesc{
MALE("男"), FEMALE("女");
//定義方法
override fun info() {
when (this) {
MALE -> println("男的")
FEMALE -> println("女的")
}
}
}
枚舉類實(shí)現(xiàn)接口不過(guò)如此,與普通類實(shí)現(xiàn)接口完全一樣:同樣要將被實(shí)現(xiàn)的接口放在英文冒號(hào)后面,并實(shí)現(xiàn)接口中包含的抽象方法 。
包含抽象方法的抽象枚舉類
假設(shè)有一個(gè) Operation枚舉類,它的 4個(gè)枚舉值 PLUS,MINUS,TIMES, DIVIDE分別代表加、減、乘、除 4 種運(yùn)算,該枚舉類需要定義一個(gè) eval()方法來(lái)完成計(jì)算。 從上面描述可以看出Operation 需要讓PLUS,MINUS,TIMES, DIVIDE這 4 個(gè)值對(duì) eval()方法各有不同的實(shí)現(xiàn)。此時(shí)可考慮為 Operation枚舉類定義一個(gè)eval()抽象方法,然后讓4個(gè)枚舉值分別為 eval()提供不同的實(shí)現(xiàn)。例如如下代碼。
enum class Operation {
PLUS {
override fun eval(x: Double, y: Double): Double {
return x + y
}
},
MINUS {
override fun eval(x: Double, y: Double): Double {
return x - y
}
},
TIMES {
override fun eval(x: Double, y: Double): Double {
return x * y
}
},
DIVIDE {
override fun eval(x: Double, y: Double): Double = x / y
};
//為枚舉類定義一個(gè)抽象方法
//這個(gè)抽象方法由不同的枚舉值提供不同的實(shí)現(xiàn)
abstract fun eval(x: Double, y: Double): Double
}
fun main(args: Array<String>) {
println(Operation.PLUS.eval(5.6,6.7))
}
編譯上面程序,會(huì)生成 5 個(gè) class 文件,其中 Operation 對(duì)應(yīng)一個(gè) class 文件,它的4個(gè)匿名內(nèi)部子類各對(duì)應(yīng)一個(gè) class文件。
枚舉類中包含抽象方法時(shí)依然不能使用 abstract 修飾枚舉類(因?yàn)橄到y(tǒng)自動(dòng)會(huì)為它添加 abstract 關(guān)鍵字),但因?yàn)槊杜e類需要顯式創(chuàng)建枚舉值,而不是作為父類,所以在定義每個(gè)枚舉值時(shí)必須為抽象成員提供實(shí)現(xiàn),否則將出現(xiàn)編譯錯(cuò)誤。
上面程序中的代碼看起來(lái)有些奇怪:當(dāng)創(chuàng)建 PLUS,MINUS,TIMES, DIVIDE 枚舉值時(shí),后面又緊跟了一對(duì)花括號(hào),這對(duì)花括號(hào)中包含了一個(gè) eval()方法定義。如果讀者還記得對(duì)象表達(dá)式(或匿名內(nèi)部類)語(yǔ)法的話,則可能就熟悉這樣的語(yǔ)法了,花括號(hào)部分實(shí)際上 就是一個(gè)類體部分,在這種情況下,當(dāng)創(chuàng)建PLUS,MINUS,TIMES, DIVIDE枚舉值時(shí) , 并不是直接創(chuàng)建 Operation 枚舉類的實(shí)例的,而是相當(dāng)于創(chuàng)建 Operation的匿名子類的實(shí)例。因?yàn)榇a的括號(hào)部分實(shí)際上是對(duì)象表達(dá)式(匿名內(nèi)部類)的類體部分,所以這個(gè)部分的代碼語(yǔ)法與前面介紹的對(duì)象表達(dá)式(匿名內(nèi)部類)的語(yǔ)法大致相似。
類委托和屬性委托
委托是 Kotlin 的另一個(gè)特色功能,也是Java原本所不具備的功能。Kotlin的委托可分為類委托和屬性委托。
類委托
類委托是代理模式的應(yīng)用,而代理模式可作為繼承的一個(gè)不錯(cuò)的替代。類委托的本質(zhì)就是將本類需要實(shí)現(xiàn)的部分方法委托給其他對(duì)象,相當(dāng)于借用其他對(duì)象的方法作為自己的實(shí)現(xiàn) 。
例如,下面定義一個(gè)類,該類實(shí)現(xiàn)了一個(gè)接口,但該類并不實(shí)現(xiàn)該接口中的抽象方法,而是借用其他對(duì)象中的方法來(lái)實(shí)現(xiàn),被借用的對(duì)象被稱為被委托對(duì)象。
interface Outputable{
fun output(msg:String)
var type:String
}
//定義一個(gè) DefaultOutput 類實(shí)現(xiàn) Outputable 接口
class DefaultOutput :Outputable{
override fun output(msg: String) {
for (i in 1..6){
println("${msg} ${i}")
}
}
override var type: String = "輸出設(shè)備"
}
//定義 Printer類,指定構(gòu)造參數(shù)b作為委托對(duì)象
class Printer(b:DefaultOutput) :Outputable by b
//定義 Projector 類,指定新建的對(duì)象作為委托對(duì)象
class Projector():Outputable by DefaultOutput(){
override fun output(msg: String) {
javax.swing.JOptionPane.showMessageDialog(null,msg)
}
}
fun main(args: Array<String>) {
val output =DefaultOutput()
//Printer 對(duì)象的委托對(duì)象是 output
var printer = Printer(output)
//其實(shí)就是調(diào)用委托對(duì)象的 output ()方法
printer.output("xq")
//Projector 對(duì)象的委托對(duì)象也是 output
var p = Projector()
//Projector 本身重寫了 output ()方法,所以此處是調(diào)用本類重寫的方法
p.output("xy")
//其實(shí)就是調(diào)用委托對(duì)象的 type 屬性方法
println(p.type)
}
上面程序定義了 Printer 和 Projector 兩個(gè)類,這兩個(gè)類都實(shí)現(xiàn)了 Ouputable 接口,因此需要實(shí)現(xiàn)該接口中的抽象方法和抽象屬性。程序通過(guò) by關(guān)鍵字為這兩個(gè)類指定了委托對(duì)象,這 意味著這兩個(gè)類可直接“借用”被委托對(duì)象所實(shí)現(xiàn)的方法和屬性。
上面程序中 Printer類雖然沒(méi)有實(shí)現(xiàn) output()方法和 type屬性,但它的實(shí)例 一樣可以在 main() 函數(shù)中調(diào)用 output()方法和 type 屬性 。 Projector類雖然指定了 DefaultOutput對(duì)象作為委托,但 由于該類本身也實(shí)現(xiàn)了 output()方法,因此當(dāng) Projector 對(duì)象調(diào)用 output()方法時(shí),它不再需要調(diào)用委托對(duì)象的 output()方法,而是直接使用自己實(shí)現(xiàn)的方法。
由此可見(jiàn),當(dāng)一個(gè)類重寫了委托對(duì)象所包含的方法之后, Kotlin 將優(yōu)先使用該類自己實(shí)現(xiàn)的方法。
上面程序示范了為類指定委托對(duì)象的兩種方式,第一種方式是通過(guò)構(gòu)造器參數(shù)指定委托對(duì) 象;第二種方式是直接在類定義的 by后新建對(duì)象。通常會(huì)采用第一種方式, 因?yàn)檫@樣可以讓多個(gè)對(duì)象共享同一個(gè)委托對(duì)象。
屬性委托
Kotlin 也支持屬性委托,屬性委托可以將多個(gè)類的類似屬性統(tǒng)一交給委托對(duì)象集中實(shí)現(xiàn),這樣就可避免每個(gè)類都需要單獨(dú)實(shí)現(xiàn)這些屬性。
對(duì)于指定了委托對(duì)象的屬性而言,由于它的實(shí)現(xiàn)邏輯己經(jīng)交給委托對(duì)象處理,因此開(kāi)發(fā)者不能為委托屬性提供 getter 和 setter 方法, Kotlin也不會(huì)為委托屬性提供 getter、 setter方法的默認(rèn)實(shí)現(xiàn)。
一旦將某個(gè)對(duì)象指定為屬性的委托對(duì)象,該對(duì)象就會(huì)全面接管該屬性的讀取( getter)和寫入( setter)操作,因此屬性的委托對(duì)象無(wú)須實(shí)現(xiàn)任何接口,但一定要提供一個(gè) getValue()方法和 setValue()方法(val 屬性無(wú)須提供 setValue方法)。
對(duì)于只讀屬性(用 val 聲明的屬性)而言,由于程序無(wú)須設(shè)置屬性值,只要能讀取屬性值即可,因此只讀屬性的委托對(duì)象只需提供使用 operator修飾的 getValue()方法。該方法的參數(shù)和返回值要求如下。
- thisRef:該參數(shù)代表屬性所屬的對(duì)象,因此該參數(shù)的類型必須是屬性所屬對(duì)象的類型 (對(duì)于擴(kuò)展屬性,指被擴(kuò)展的類型)或其超類型。
- property:該參數(shù)代表目標(biāo)屬性,該參數(shù)的類型必須是 KPropetry<*>或其超類型 。
- 返回值: 該方法必須返回與目標(biāo)屬性相同的類型(或其子類型)。
對(duì)于讀寫屬性(用 var聲明的屬性)而言,屬性的委托對(duì)象必須額外提供一個(gè)使用 operator修飾的 setValue()方法,該方法不需要返回值,但必須指定如下參數(shù)。
- thisRef: 與 getValue()的 thisRef參數(shù)的作用相同。
- property: 與 getValue()的property參數(shù)的作用相同。
- newValue: 該參數(shù)代表目標(biāo)屬性新設(shè)置的屬性值,該參數(shù)的類型必須具有和目標(biāo)屬性相同的類型或其超類型 。
屬性的委托對(duì)象的 getValue()、setValue()方法既可以是成員方法,也可以是擴(kuò)展方法??傊灰欠弦?guī)則的兩個(gè)方法均可。
Kotlin標(biāo)準(zhǔn)庫(kù)在kotlin.properties下提供了 ReadOnlyProperty和 ReadWriteProperty兩個(gè)接口,其中ReadOnlyProperty接口定義了一個(gè)符合只讀屬性委托標(biāo)準(zhǔn)的getValue()抽象方法,因此該接口的實(shí)現(xiàn)類可作為只讀屬性的委托對(duì)象;而ReadWriteProperty接口定義了符合讀寫屬性委托標(biāo)準(zhǔn)的 getValue()和 setValue()抽象方法,因此該接口的實(shí)現(xiàn)類可作為讀寫屬性的委托對(duì)象。
import kotlin.reflect.KProperty
class PropertyDelegation {
//該屬性的委托對(duì)象是 MyDelegation
var name: String by MyDelegation()
}
class MyDelegation {
private var _backValue = "默認(rèn)值"
operator fun getValue(thisRef: PropertyDelegation,
property: KProperty<*>): String {
println("${thisRef}的${property.name}屬性執(zhí)行g(shù)etter方法")
return _backValue
}
operator fun setValue(thisRef: PropertyDelegation,
property: KProperty<*>, newValue: String) {
println("${thisRef}的${property.name}屬性執(zhí)行 setter 方法,傳入?yún)?shù)值為:${newValue}")
_backValue = newValue
}
}
fun main(args: Array<String>) {
val pd = PropertyDelegation()
//讀取屬性,實(shí)際上是調(diào)用屬性的委托對(duì)象的 getter 方法
println(pd.name)
//寫入屬性,實(shí)際上是調(diào)用屬性的委托對(duì)象的 setter 方法
pd.name = "kotlin"
println(pd.name)
}
上面代碼將PropertyDelegation的 name 屬性委托給 MyDelegation 對(duì)象。接 下來(lái)程序定義了 MyDelegation 類,由于該類的對(duì)象要作為讀寫屬性的委托對(duì)象,因此該類必須實(shí)現(xiàn)符合要求的 getter、 setter方法,如 MyDelegation中代碼所示。
當(dāng)程序讀取 PropertyDelegation對(duì)象的 name 屬性時(shí),實(shí)際上就是執(zhí)行 MyDelegation對(duì)象所提供的 getValue()方法;當(dāng)程序?qū)?PropertyDelegation對(duì)象的 name 屬性賦值時(shí),實(shí)際上就是執(zhí)行 MyDelegation對(duì)象所提供的setValue()方法。
實(shí)際上,上面的MyDelegation類已經(jīng)實(shí)現(xiàn)了ReadWriteProperty接口的規(guī)范,因此程序完全可讓該類實(shí)現(xiàn) ReadWriteProperty < PropertyDelegation, String>接口,再為getValue()和setValue()方法添加override修飾符。
前面己經(jīng)講過(guò), Kotlin不會(huì)為委托屬性生成幕后字段、 getter和 setter方法,這是不言而喻的,因此委托屬性的所有處理細(xì)節(jié)都由委托對(duì)象負(fù)責(zé) 。Kotlin會(huì)委托屬性生成輔助屬性,該輔助屬性引用了屬性的委托對(duì)象。當(dāng)程序調(diào)用委托屬性的 getter、 setter方法時(shí),實(shí)際上就是執(zhí)行委托對(duì)象的 getValue()、 setValue()方法。例如,我們定義了如下委托屬性 :
class C{
var prop: Type by MyDelegation()
}
上面代碼定義了名為prop的委托屬性,該屬性的委托對(duì)象是MyDelegate。上面代碼被編譯器處理之后將生成如下代碼 :
//這段代碼是由編譯器生成的
class C {
//生成隱藏屬性 ,隱藏屬 性引用代理對(duì)象
private val prop$delegate = MyDelegation()
var prop: Type
// getter 方法,實(shí)際上就是調(diào)用代理對(duì)象的 getValue ()方法
get() = prop$delegate.getValue(this, this::prop)
//setter 方法,實(shí)際上就是調(diào)用代理對(duì)象的 setValue ()方法
set(value:Type) {
prop$delegate.setValue(this, this::prop,value)
}
}
在上面編譯器生成的 getter、 setter 方法實(shí)現(xiàn)中,其實(shí)就是調(diào)用代理對(duì)象的 getValue()、 setValue()方法,傳入的第一個(gè)參數(shù) this代表正在調(diào)用 getter或 setter方法,第二個(gè)參數(shù) this::prop 代表正在操作的目標(biāo)屬性 。
延遲屬性
Kotlin 提供了 一個(gè)lazy()函數(shù),該函數(shù)接受一個(gè)Lambda表達(dá)式作為參數(shù),并返回一個(gè) Lazy<T>對(duì)象 。
Lazy<T>對(duì)象包含了一個(gè)符合只讀屬性委托要求的 getValue()方法,因此該 Lazy<T>對(duì)象可作為只讀屬性的委托對(duì)象 。Lazy<T>對(duì)象只能作為只讀屬性的委托對(duì)象 。
val lazyProp: String by lazy {
println("第一次訪問(wèn)時(shí)執(zhí)行代碼塊")
"kotlin"
}
fun main(args: Array<String>) {
//兩次訪問(wèn) lazyProp 屬性
println(lazyProp)
println(lazyProp)
}
上面代碼將 lazyProp 屬性的委托對(duì)象指定為 lazy()函數(shù)的返回值: Lazy<T> 對(duì)象。這樣當(dāng)程序訪問(wèn) lazyProp 屬性時(shí),實(shí)際上就是調(diào)用 Lazy<T>對(duì)象的 getValue()方法。
Lazy<T>的 getValue()方法的處理邏輯是:第一次調(diào)用該方法時(shí),程序會(huì)計(jì)算 Lambda 表達(dá)式,并得到其返回值,以后程序再次調(diào)用該方法時(shí),不再計(jì)算 Lambda表達(dá)式,而是直接使用第一次計(jì)算得到的返回值。
lazy()函數(shù)有如下兩個(gè)版本。
- fun <T> lazy(initializer: () -> T): Lazy<T>
- fun <T> lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T>
從上面代碼可以看出 , 前面代碼使用的是第一個(gè)版本,該版本的lazy()函數(shù)執(zhí)行Lambda表達(dá)式時(shí)會(huì)提供自動(dòng)添加保證線程安全的同步鎖,因此開(kāi)銷略大。
上面第一個(gè)lazy()函數(shù)相當(dāng)于第二個(gè)lazy()函數(shù)的第一個(gè)參數(shù)指定為L(zhǎng)azyThreadSafetyMode.SYNCHRONIZED
如果程序不需要 lazy()函數(shù)保證線程安全,希望提供更好的性能,則可將 lazy()函數(shù)的第一個(gè)參數(shù)指定為如下兩種模式。
- LazyThreadSafetyMode.PUBLICATION : 在這種模式下,lazy()函數(shù)不會(huì)使用排他同步鎖,多個(gè)線程可同時(shí)執(zhí)行。
- LazyThreadSafetyMode.NONE: 在這種模式下,lazy()函數(shù)不會(huì)有任何線程安全相關(guān)的操作和開(kāi)銷 。
例如,將上面的 lazyProp 改為如下形式來(lái)取消線程安全保證,從而獲得最佳性能
val lazyProp: String by lazy (LazyThreadSafetyMode.NONE){
println("第一次訪問(wèn)時(shí)執(zhí)行代碼塊")
"kotlin"
}
屬性監(jiān)聽(tīng)
Kotlin的kotlin.properties包下提供了一個(gè)Delegates對(duì)象聲明 (單例對(duì)象),該對(duì)象中包含如下兩個(gè)常用方法 。
- fun<T> observable(initialValue: T, onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Unit): ReadWriteProperty<Any?, T>: 該方法返回 一個(gè)ReadWriteProperty對(duì)象,因此該方法的返回值可作為讀寫屬性的委托對(duì)象。該方法的第一個(gè)參數(shù)用于為接受委托的屬性指定初始值,第二個(gè)參數(shù)可接受 Lambda 表達(dá)式, 當(dāng)接受委托的屬性被設(shè)置值時(shí),該 Lambda表達(dá)式就會(huì)被觸發(fā) 。
- fun <T> vetoable(initialValue: T, onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Boolean): ReadWritePrope<Any?, T>: 該方法與前一個(gè)方法基本相同, 區(qū)別只是負(fù)責(zé)執(zhí)行監(jiān)聽(tīng)的 Lambda 表達(dá)式需要返回值,當(dāng)該返回值為 true 時(shí),接受委托的屬性的新值設(shè)置成功; 否則設(shè)置失敗。
例如如下程序。
import kotlin.properties.Delegates
var observableProp: String by Delegates.observable("默認(rèn)值") {
//Lambda表達(dá)式的第一個(gè)參數(shù)代表被監(jiān)聽(tīng)的屬性
//第二個(gè)參數(shù)代表修改之前的值
//第三個(gè)參數(shù)代表被設(shè)置的新值
prop, old, new ->
println("${prop}的${old}被改為${new}")
}
fun main(args: Array<String>) {
//訪問(wèn) observableProp 屬性 , 不會(huì)觸發(fā)監(jiān)聽(tīng)器的 Lambda 表達(dá)式
println(observableProp)
//設(shè)置屬性值,觸發(fā)監(jiān)聽(tīng)器的 Lambda 表達(dá)式
observableProp ="kotlin"
println(observableProp)
}
從上面的運(yùn)行結(jié)果可以看出,無(wú)論我們?yōu)閷傩栽O(shè)置的新值是什么,總可以設(shè)置成功。如果需要對(duì)新值進(jìn)行判斷,然后再確定是否設(shè)置成功,則可使用 Delegates 對(duì)象的 vetoable()方法 。 例如如下代碼:
import kotlin.properties.Delegates
var observableProp: Int by Delegates.vetoable(20) {
//Lambda表達(dá)式的第一個(gè)參數(shù)代表被監(jiān)聽(tīng)的屬性
//第二個(gè)參數(shù)代表修改之前的值
//第三個(gè)參數(shù)代表被設(shè)置的新值
prop, old, new ->
println("${prop}的${old}被改為${new}")
new > old
}
fun main(args: Array<String>) {
//訪問(wèn) observableProp 屬性 , 不會(huì)觸發(fā)監(jiān)聽(tīng)器的 Lambda 表達(dá)式
println(observableProp)
//新值小于舊值,監(jiān)聽(tīng)的 Lambda 表達(dá)式返回 false,新值設(shè)置失敗
observableProp = 15
println(observableProp) //輸出 20
//新值大于舊值,監(jiān)聽(tīng)的 Lambda 表達(dá)式返回 true,新值設(shè)置成功
observableProp =25
println(25)
}
上面 Lambda表達(dá)式內(nèi)最后的表達(dá)式就是它的返回值,如果 new>old,則返回 true; 否則返回 false。由此可見(jiàn),此處我們要求新設(shè)置的屬性值必須比原屬性值大才能設(shè)置成功。
現(xiàn)代編程語(yǔ)言幾乎都提供了屬性監(jiān)聽(tīng)的功能,由于 Java 并沒(méi)有真正意義上的 屬性( Java 的屬性等于field、 getter、 setter方法的組合),因此 Java對(duì)屬性的監(jiān)聽(tīng)需要通過(guò) setter 方法來(lái)實(shí)現(xiàn) 。而 Kotlin 的屬性監(jiān)聽(tīng)則通過(guò)屬性委托機(jī)制來(lái)實(shí)現(xiàn)。
使用Map存儲(chǔ)屬性值
Kotlin 的 Map 提供了如下方法:
operator fun <V, V1 : V> Map<in String, V>.getValue(thisRef: Any?, property:KProperty<*>): V1
該方法符合只讀屬性的委托對(duì)象的要求,這意味著 Map 對(duì)象可作為只讀對(duì)象的委托 。
MutableMap 則 提供如下兩個(gè)方法:
- operator fun <V, V1 : V> Map<in String, V>.getValue(thisRef: Any?, property:KProperty<*>): V1
- operator fun <V> MutableMap<in String, in V>.setValue(thisRef: Any?, property:
KProperty<*>, value: V)
上面兩個(gè)方法符合讀寫屬性的委托對(duì)象的要求,這意味著 MutableMap對(duì)象可作為讀寫對(duì)象的委托。
因此程序可將類只讀屬性委托給 Map對(duì)象管理,在這種情況下,對(duì)象本身并不負(fù)責(zé)存儲(chǔ)對(duì)象狀態(tài),而是將對(duì)象狀態(tài)保存在 Map 集合中。這么做的好處是,當(dāng)程序需要與外部接口(如JSON)通信時(shí),程序并不需要將該對(duì)象直接暴露出來(lái),只要將該對(duì)象屬性所委托的Map暴露出來(lái)即可,而 Map 則完整地保存了整個(gè)對(duì)象狀態(tài)。
例如,如下程序使用 Map 存儲(chǔ)對(duì)象的只讀屬性。
class Item(val map: Map<String, Any?>) {
val barCode: String by map
val name: String by map
val price: Double by map
}
fun main(args: Array<String>) {
val item = Item(mapOf(
"barCode" to "133355 ",
"name" to "kotlin",
"price" to 68.9
))
println(item.barCode)
println(item.name)
println(item.price)
println("--------------")
//將對(duì)象持有的 map 暴露出來(lái),其他程序可通過(guò)標(biāo)準(zhǔn) Map 讀取數(shù)據(jù)
val map = item.map
println(map["barCode"])
println(map["name"])
println(map["price"])
}
從上面代碼可以看出,程序可以將 Item對(duì)象的所有只讀屬性都委托給一個(gè)Map對(duì)象,其實(shí)道理很簡(jiǎn)單 :Item對(duì)象的每個(gè)屬性名就相當(dāng)于 Map 的 key,屬性值就相當(dāng)于 key對(duì)應(yīng)的value。指定Map 對(duì)象作為 Item對(duì)象的委托之后,接下來(lái)程序只要把 Item 的 Map 暴露出來(lái),程序即可通過(guò)該 Map來(lái)獲取 Item對(duì)象的狀態(tài)。
如果對(duì)象包含的屬性是讀寫屬性,則需要使用 MutableMap作為委托對(duì)象 。例如如下程序:
class Mutableltem(var map :MutableMap<String,Any?>){
var barCode: String by map
var name: String by map
var price: Double by map
}
fun main(args: Array<String>) {
val item = Mutableltem(mutableMapOf())
//設(shè)置 item 對(duì)象的屬性,其實(shí)會(huì)委托給 MutableMap 處理
item.barCode="111"
item.name= "kotlin"
item.price =20.1
//將對(duì)象持有的 map 暴露出來(lái),其他程序可通過(guò)標(biāo)準(zhǔn) Map 讀取數(shù)據(jù)
val map = item.map
println(map["barCode"])
println(map["name"])
println(map["price"])
}
上面程序?qū)?Mutableltem 的讀寫屬性委托給 MutableMap 對(duì)象,這樣當(dāng)程序設(shè)置 Mutableltem對(duì)象的屬性值時(shí),實(shí)際上是交給 MutableMap 負(fù)責(zé)處理的,相當(dāng)于為 MutableMap 設(shè)置了key-value對(duì)。 因此程序后面可通過(guò) MutableMap獲取到為 Item設(shè)置的屬性值。
反過(guò)來(lái),如果程序?yàn)楸晃械?MutableMap 對(duì)象設(shè)置了 key-value 對(duì),實(shí)際上就是修改了Item對(duì)象的狀態(tài)。
局部屬性委托
從 Kotlin 1.1 開(kāi)始, Kotlin 支持為局部變量指定委托對(duì)象。這種指定了委托對(duì)象的局部變量被稱為“局部委托屬性”,其實(shí)還是局部變量,只是對(duì)該變量的讀取、賦值操作將會(huì)交給委托對(duì)象去實(shí)現(xiàn)。
與前面介紹的屬性委托相似 ,局部變量的委托對(duì)象同樣要遵守一定的要求。對(duì)于只讀屬性而言,同樣需要實(shí)現(xiàn) operator修飾的 getValue(thisRef: Nothing?, property: !<Property<*>)方法, 注意該方法的第一個(gè)參數(shù)是 Nothing?類型, Nothing 是 Kotlin 提供的 一個(gè)特殊的類,專門用于代表永不存在的對(duì)象。
由于局部變量不屬于任何對(duì)象, 因此它的委托對(duì)象的 getValue()方法的第一個(gè)參數(shù)自然也就不存在了,因此 Kotlin使用 Nothing?來(lái)聲明 getValue()方法的第一個(gè)參數(shù)的類型 。
對(duì)于讀寫屬性而言,則需要實(shí)現(xiàn) getValue()和 setValue()兩個(gè)方法,其中setValue()方法的第一個(gè)參數(shù)的類型也應(yīng)該聲明為 Nohting?,道理是一樣的。
下面是一個(gè)局部屬性委托的示例:
import kotlin.reflect.KProperty
class MyDelegation {
private var _backValue = "默認(rèn)值"
operator fun getValue(thisRef: Nothing?,
property: KProperty<*>): String {
println("${thisRef}的${property.name}屬性執(zhí)行g(shù)etter方法")
return _backValue
}
operator fun setValue(thisRef: Nothing?,
property: KProperty<*>, newValue: String) {
println("${thisRef}的${property.name}屬性執(zhí)行 setter 方法,傳入?yún)?shù)值為:${newValue}")
_backValue = newValue
}
}
fun main(args: Array<String>) {
var name:String by MyDelegation()
//訪問(wèn)局部變量 , 實(shí)際上是調(diào)用 MyDelegation 對(duì)象的 getValue()方法
println(name)
//對(duì)局部變量賦值,實(shí)際上是調(diào)用 MyDelegation 對(duì)象的 setValue()方法
name = "kotlin"
println(name)
}
從上面代碼可以看出,局部變量的委托對(duì)象的 getValue()和setValue()方法的第一個(gè)參數(shù)類型變成了 Nothing?,其他變化不大。
與普通屬性類似的是,程序當(dāng)然也可以利用 Kotlin所提供的標(biāo)準(zhǔn)委托,比如可以利用 lazy() 函數(shù)對(duì)局部變量延遲初始化 。程序如下:
fun main(args: Array<String>) {
val name :String by lazy{
println("”計(jì)算 name局部變量")
"kotlin"
}
//第一 次訪問(wèn) name 屬性時(shí),會(huì)執(zhí)行 Lambda 表達(dá)式
println(name)
//以后再次訪問(wèn) name 屬性時(shí),則直接使用第一次計(jì)算的值
println(name)
}
委托工廠
除提供 getValue()、 setValue()方法的對(duì)象可作為屬性的委托對(duì)象之外,從 Kotlin1.1 開(kāi)始, 一種類似于“委托工廠”的對(duì)象也可作為委托對(duì)象。委托工廠需要提供如下方法。
- operator fun provideDelegate(thisRef: Any?, prop: KProperty<*>): 該方法的兩個(gè)參數(shù)與委托的 getValue()方法的兩個(gè)參數(shù)的意義相同 。
如果上面方法返回 ReadOnlyPrope即對(duì)象,那么該對(duì)象可作為只讀屬性的委托對(duì)象 ; 如果返回 ReadWriteProperty對(duì)象,則該對(duì)象就可作為讀寫屬性的委托對(duì)象。
使用 provideDelegate()方法來(lái)生成委托的好處是,Kotlin 會(huì)保證對(duì)象的屬性被初始化時(shí)調(diào)用該方法來(lái)生成委托,這樣我們即可在該方法中加入任何自定義代碼,完成任何自定義邏輯。
下面示例示范了“委托工廠”的用法:
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
class MyTarget {
//該屬性的委托對(duì)象是 provideDelegate()方法返回的 MyDelegation對(duì)象
var name: String by PropertyChecker()
}
class PropertyChecker {
operator fun provideDelegate(thisRef: MyTarget,
prop: KProperty<*>): ReadWriteProperty<MyTarget, String> {
//插入口定義代碼,可執(zhí)行任意業(yè)務(wù)操作
checkProperty(thisRef, prop.name)
//返回實(shí)際的委托對(duì)象
return MyDelegation()
}
private fun checkProperty(thisRef: MyTarget, name: String) {
println("”---檢查屬性----")
}
}
class MyDelegation : ReadWriteProperty<MyTarget, String> {
private var _backValue = "默認(rèn)值"
override fun getValue(thisRef: MyTarget, property: KProperty<*>): String {
println("${thisRef}的${property.name}屬性執(zhí)行g(shù)etter方法")
return _backValue
}
override fun setValue(thisRef: MyTarget, property: KProperty<*>, value: String) {
println("${thisRef}的${property.name}屬性執(zhí)行 setter 方法,傳入?yún)?shù)值為:${value}")
_backValue = value
}
}
fun main(args: Array<String>) {
//創(chuàng)建對(duì)象(初始化屬性),調(diào)用委托工廠的 provideDelegate()方法
var pd =MyTarget()
//讀取屬性 ,實(shí)際上是調(diào)用屬性的委托對(duì)象的 getter 方法
println(pd.name)
//寫入屬性 ,實(shí)際上是調(diào)用屬性的委托對(duì)象的 setter 方法
pd.name ="java"
println(pd.name)
}
上面代碼指定屬性的委托對(duì)象為PropertyChecker,該對(duì)象是一個(gè)委托工廠,它提供了provideDelegate()方法來(lái)生成委托,該方法在生成委托之前先調(diào)用 checkProperty()方法執(zhí)行檢查,具體檢查什么,由開(kāi)發(fā)者自己決定;然后該方法才返回真正的委托對(duì)象: MyDelegation。 從上面描述可以看出,使用委托工廠的好處在于:程序可以在屬性初始化時(shí)自動(dòng)回調(diào)委托工廠的 provideDelegate()方法,而該方法則可以執(zhí)行自己的業(yè)務(wù)操作。
屬性的委托依然由 MyDelegation 對(duì)象負(fù)責(zé),只是在屬性初始化時(shí)插入了業(yè)務(wù)檢查方法: checkProperty()。