嵌套類和內(nèi)部類
大部分時(shí)候辆琅,類被定義成一個(gè)獨(dú)立的程序單元漱办。在某些情況下,也會把一個(gè)類放在另一個(gè)類的內(nèi)部定義婉烟,這個(gè)定義在其他類內(nèi)部的類就被稱為嵌套類(有的地方也叫寄生類)娩井,包含嵌套類的類被稱為外部類(有的地方也叫宿主類) 。
Java 的內(nèi)部類可分為兩種:靜態(tài)內(nèi)部類 (有 static 修飾)和非靜態(tài)內(nèi)部類(無 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 中無 static 修飾的非靜態(tài)內(nèi)部類 。
嵌套類主要有如下作用:
- 嵌套類提供了更好的封裝婴谱,可以把嵌套類隱藏在外部類之內(nèi)蟹但,不允許同一個(gè)包中的其他類訪問該類 。 假設(shè)需要?jiǎng)?chuàng)建 Cow 類谭羔, Cow 類需要組合一個(gè) CowLeg對象华糖, CowLeg類只有在Cow 類中才有效,離開了 Cow 類之后沒有任何意義瘟裸。在這種情況下客叉,就可以把 CowLeg 定義成 Cow 的嵌套類,不允許其他類訪問CowLeg。
- 內(nèi)部類(相當(dāng)于 Java 的非靜態(tài)內(nèi)部類)成員可以直接訪問外部類的私有數(shù)據(jù)兼搏,因?yàn)閮?nèi)部類被當(dāng)成其外部類成員卵慰,同一個(gè)類的成員之間可以互相訪問。但外部類不能訪問內(nèi)部類的實(shí)現(xiàn)細(xì)節(jié)佛呻,例如內(nèi)部類的屬性裳朋。
從語法的角度來看,定義嵌套類(內(nèi)部類)與定義外部類的語法大致相同吓著,只是嵌套類(內(nèi)部類)可使用 protected修飾鲤嫡,用于表示該嵌套類(內(nèi)部類)可在其外部類的子類中被訪問。 定義嵌套類(內(nèi)部類)非常簡單绑莺,只要把一個(gè)類放在另一個(gè)類的類內(nèi)部嵌套定義即可暖眼。此處的“類內(nèi)部”包括類中的任何位置,甚至在方法中也可以定義嵌套類(方法中定義的嵌套類被稱為局部嵌套類) 紊撕。 嵌套類(內(nèi)部類)定義的語法格式如下:
class OuterClass{
//此處可以定義嵌套類罢荡、內(nèi)部類
}
內(nèi)部類
內(nèi)部類(等同于Java 的非靜態(tài)內(nèi)部類)相當(dāng)于外部類的實(shí)例成員赡突,因此它可以直接訪問外部類的所有成員 对扶。
下面程序在 Cow類中定義了 一個(gè) CowLeg 內(nèi)部類,并在 CowLeg類的方法中直接訪問 Cow 的 private 屬性惭缰。
//通過主構(gòu)造器為外部類定義屬性
class Cow(var weight: Double = 0.0) {
//定義一個(gè)內(nèi)部類(用 inner 修飾浪南,相當(dāng)于 Java 的非靜態(tài)內(nèi)部類)
//通過主構(gòu)造器為內(nèi)部類定義屬性
private inner class CowLeg(var length: Double = 0.0, var color: String = "") {
//內(nèi)部類的方法
fun info() {
:("當(dāng)前牛腿顏色是:${color},高:${length}")
//直接訪問外部類的 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修飾符來修飾這個(gè)類絮记。外部類 Cow 中包含了 一個(gè) test()方法,該方法中創(chuàng)建了一個(gè) CowLeg 對象虐先,并調(diào)用了該對象的 info()方法怨愤。不難發(fā)現(xiàn),在外部類中使用內(nèi)部類時(shí)蛹批,與平時(shí)使用普通類并沒有太大的區(qū)別撰洗。
編譯上面程序,將看到在文件所在路徑下生成了兩個(gè) class 文件腐芍,其中一個(gè)是 Cow.class, 另 一個(gè)是 Cow$CowLeg.class差导,前者是外部類 Cow 的 class 文件,后者是內(nèi)部類 CowLeg 的 class 文件猪勇,即成員嵌套類和成員內(nèi)部類的 class 文件總是這種形式: OuterClass$InnerClass.class设褐。
前面提到過,在內(nèi)部類中可以直接訪問外部類的 private 成員,上面代碼就有在CowLeg類的方法內(nèi)直接訪問其外部類的private方法助析。這是因?yàn)?在內(nèi)部類對象中 裁替,保存了一個(gè)它所寄生的外部類對象的引用 (當(dāng)調(diào)用內(nèi)部類的方法時(shí),必須使用內(nèi)部類對象作為調(diào)用者貌笨,內(nèi)部類實(shí)例必須寄生在外部類實(shí)例中)弱判。
當(dāng)在內(nèi)部類的方法內(nèi)訪問某個(gè)屬性時(shí),系統(tǒng)優(yōu)先在該方法內(nèi)查找是否存在該名字的局部變量锥惋,如果存在就使用該變量;如果不存在昌腰,則到該方法所在的內(nèi)部類中查找是否存在該名字的屬性,如果存在則使用該屬性 : 如果不存在膀跌,則到該內(nèi)部類所在的外部類中查找是否存在該名字的屬性遭商,如果存在則使用該屬性:如果依然不存在,系統(tǒng)將出現(xiàn)編譯錯(cuò)誤捅伤,提示找不到該屬性劫流。
因此,如果外部類屬性丛忆、內(nèi)部類屬性與內(nèi)部類中方法的局部變量同名祠汇,則可通過使用this、 帶標(biāo)簽的 this進(jìn)行限定來區(qū)分熄诡。
class DiscernVariable { //隱式標(biāo)簽@ DiscernVariable
private val prop = "外部類的屬性"
inner class InClass { //隱式標(biāo)簽@ InClass
private val prop = "內(nèi)部類的屬性"
fun info() {
val prop = "局部變量"
//通過外部類類名.this.varName 訪問外部類的屬性
println("外部類的屬性值:${this@DiscernVariable.prop}")
//通過this.varName 訪問內(nèi)部類的屬性
println("內(nèi)部類的屬性值:${this.prop}")
//直接訪問局部變盤
println("局部變繭的值:${prop}")
}
}
fun test() {
val ic = InClass()
ic.info()
}
}
fun main(args: Array<String>) {
DiscernVariable().test()
}
上面代碼分別訪問外部類的屬性可很、內(nèi)部類的屬性。通過帶標(biāo)簽的 this 前綴可顯式指定訪問哪個(gè)類的屬性凰浮。不帶標(biāo)簽的 this 前綴默認(rèn)訪問內(nèi)部類的屬性我抠。
Kotlin的 this,比 Java 的 this 更強(qiáng)大袜茧,通過這種帶標(biāo)簽的 this, Kotlin 可以進(jìn)行非常細(xì)致的區(qū)分菜拓。 Kotlin關(guān)于this的處理規(guī)則如下。
- 在類的方法或?qū)傩灾械严茫瑃his代表調(diào)用該方法或?qū)傩缘膶ο蟆?/li>
- 類的構(gòu)造器中纳鼎, this代表該構(gòu)造器即將返回的對象。
- 擴(kuò)展函數(shù)或帶接收者的函數(shù)字面值中递递,this 表示點(diǎn)(.)左邊的“接收者”喷橙。
- 如果 this 沒有限定符,那么它優(yōu)先代表包含該this的最內(nèi)層的接收者登舞,并且會自動向外搜索贰逾。如果要讓 this 明確引用特定的接收者,則可使用標(biāo)簽限定符菠秒。
如下程序示范了一個(gè)比較復(fù)雜的this示例疙剑,讀者可以參考該程序和注釋來理解Kotlin對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)代表該方法所屬對象: Int對象
val c1 = this@foo //顯式指定@foo標(biāo)簽言缤,與c代表的對象相同
println(a)
println(b)
println(c)
println(c1)
//為String擴(kuò)展funLit()方法
val funLit = lambda@ fun String.() {
val d = this //不帶標(biāo)簽的 this嚼蚀,默認(rèn)代表該方法所屬對象: String對象
val d1 = this@lambda //顯式指定@lambda標(biāo)簽,與 d代表的對象相同
println(d)
println(d1)
}
"fkit".funLit()
//直接定義一個(gè) Lambda 表達(dá)式管挟,沒有接收者
val funLit2 = {
//該 this 所在的 Lambda 表達(dá)式?jīng)]有接收者 轿曙,因此當(dāng)前范圍沒有 this
//系統(tǒng)會繼續(xù)向該 Lambda 表達(dá)式所在范圍搜索 this
//故此處 this 將代表 foo ()方法的接收者: Int 對象
val e = this
val e1 = this@foo ////顯式指定@foo標(biāo)簽,與 e 代表的對象相同
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對象: ${bObj}")
bObj.testB()
}
}
fun main(args: Array<String>) {
var aObj =A()
println("程序創(chuàng)建的A對象: ${aObj}")
aObj.testA()
}
內(nèi)部類的成員可以訪問外部類的 private 成員僻孝,但反過來就不成立了导帝。內(nèi)部類的成員只在內(nèi)部類范圍內(nèi)是可知的,并不能被外部類直接使用穿铆。如果外部類需要訪問內(nèi)部類的成員您单,則必須顯式創(chuàng)建內(nèi)部類對象來調(diào)用訪問其成員。
嵌套類
嵌套類相當(dāng)于 Java 的靜態(tài)內(nèi)部類荞雏,因此嵌套類直接屬于外部類的類本身虐秦,而不是外部類實(shí)例相關(guān) 。
Java語法有一條規(guī)則:靜態(tài)成員不可訪問非靜態(tài)成員凤优。而 Kotlin徹底取消了static修飾符悦陋,因此Kotlin 類中的成員除嵌套類之外,全部都是非靜態(tài)成員别洪,因此嵌套類不可訪問外部類的其他任何成員(只能訪問其他嵌套類)叨恨。
class NestedClassTest {
var prop1 = 5
fun test() {
println("外部類的test()")
}
//沒有 inner 修飾符,是嵌套類(相當(dāng)于 Java 的靜態(tài)內(nèi)部類)
class NestedClass {
fun accessOuterMember() {
//訪問另一個(gè)嵌套類是允許的
val a = A()
//下面兩行代碼都會出現(xiàn)錯(cuò)誤
//println(prop1)
//test()
}
}
class A
}
上面代碼定義了一個(gè)屬性和一個(gè)方法挖垛,NestedClass類中定義了 一個(gè)accessOuterMember()方法,但它不能訪問外部類的 propl 屬性和 test()方法秉颗。 嵌套類唯一可訪問的是外部類的其他嵌套類痢毒。 嵌套類相當(dāng)于外部類的靜態(tài)成員,因此外部類的所有方法蚕甥、屬性哪替、初始化塊都可以使用嵌套類來定義變量 、創(chuàng)建對象等菇怀。 外部類依然不能直接訪問嵌套類的成員凭舶,但可以使用嵌套類的對象作為調(diào)用者來訪問嵌套類的成員。
除此之外爱沟, Kotlin 還允許在接口中定義嵌套類帅霜,但不允許在接口中定義內(nèi)部類(即不允許定義使用 inner 修飾的內(nèi)部類)。如果為接口中的嵌套類指定訪問控制符呼伸,則只能指定 public 或 private; 如果定義接口中的嵌套類時(shí)省略訪問控制符身冀,則該嵌套類默認(rèn)是 public 訪問權(quán)限。
在外部類以外使用內(nèi)部類
定義類的主要作用就是定義變量、創(chuàng)建對象和派生子類搂根。定義內(nèi)部類的主要作用也如此珍促。正如前面所看到的,在外部類內(nèi)部使用嵌套類或內(nèi)部類時(shí)剩愧,與平常使用普通類沒有太大的區(qū)別猪叙,一樣可以直接通過嵌套類或內(nèi)部類的類名來定義變量,調(diào)用嵌套類或內(nèi)部類的構(gòu)造器來創(chuàng)建實(shí)例仁卷。
唯一要牢記的一點(diǎn):嵌套類只能訪問外部的其他嵌套類沐悦,不能訪問外部的其他任何成員。
如果希望在外部類以外的地方使用內(nèi)部類或嵌套類五督,一定要注意訪問權(quán)限的限制藏否,比如使用 private修飾的嵌套類或內(nèi)部類只能在外部類之內(nèi)使用。
在外部類以外的地方定義內(nèi)部類變量的語法格式如下
var | val varName: OuterClass.InnerClass
從上面語法格式可以看出充包,在外部類以外的地方使用內(nèi)部類時(shí)副签,內(nèi)部類完整的類名應(yīng)該是
OuterClass.InnerClass。如果外部類有包名基矮,則還應(yīng)該增加包名前綴淆储。
由于內(nèi)部類的對象必須寄生在外部類的對象中,因此在創(chuàng)建內(nèi)部類對象之前家浇,必須先創(chuàng)建
其外部類對象本砰。 在外部類以外的地方創(chuàng)建內(nèi)部類實(shí)例的語法格式如下
Outerinstance.InnerConstructor()
從上面語法格式可以看出,在外部類以外的地方創(chuàng)建內(nèi)部類實(shí)例時(shí)必須使用外部類實(shí)例來調(diào)用內(nèi)部類的構(gòu)造器钢悲。下面程序示范了如何在外部類以外的地方創(chuàng)建內(nèi)部類對象点额,并把它賦值給內(nèi)部類類型的變量。
class Out {
//定義一個(gè)內(nèi)部類莺琳,不使用訪問控制符还棱,默認(rèn)是 public
inner class In(msg: String) {
init {
println(msg)
}
}
}
fun main(args: Array<String>) {
var oi :Out.In = Out().In("測試信息")
}
從上面代碼可以看出,內(nèi)部類的構(gòu)造器必須使用外部類對象來調(diào)用惭等。
在外部類以外使用嵌套類
因?yàn)榍短最愂菍儆谕獠款惖念惐旧淼恼涫郑虼藙?chuàng)建嵌套類對象時(shí)無須創(chuàng)建外部類對象,所以嵌套類用起來非常方便辞做。在外部類以外的地方創(chuàng)建嵌套類實(shí)例的語法格式如下:
OuterClass. NestedConstructor()
class NestedOut {
//定義一個(gè)嵌套類 琳要, 不使用訪問控制符,默認(rèn)是 public
open class Nested {
init {
println("嵌套類的構(gòu)造器")
}
}
}
fun main(args: Array<String>) {
var nn :NestedOut.Nested = NestedOut.Nested()
}
從上面代碼可以看出秤茅,不管是內(nèi)部類還是嵌套類稚补,其聲明變量的語法完全一樣。區(qū)別只是在創(chuàng)建對象時(shí)嫂伞,嵌套類只需使用外部類即可調(diào)用構(gòu)造器孔厉,而內(nèi)部類必須使用外部類對象來調(diào)用構(gòu)造器拯钻。
因?yàn)樵谡{(diào)用嵌套類的構(gòu)造器時(shí)無須使用外部類對象,所以創(chuàng)建嵌套類的子類也比較簡單撰豺。 下面代碼就為嵌套類 NestedSubClass 定義了一個(gè)空的子類 粪般。
class NestedSubClass :NestedOut.Nested()
相比之下,使用嵌套類比使用內(nèi)部類要簡單很多污桦,只要把外部類當(dāng)作嵌套類的包空間即可亩歹。 因此程序應(yīng)該優(yōu)先考慮使用嵌套類。
局部嵌套類
如果把一個(gè)嵌套類放在方法或函數(shù)中定義凡橱,則這個(gè)嵌套類就是一個(gè)局部嵌套類小作,局部嵌套類僅在該方法或函數(shù)中有效。由于局部嵌套類不能在方法或函數(shù)以外的地方使用稼钩,因此局部嵌套類也不能使用訪問控制符修飾顾稀。
如果需要用局部嵌套類定義變量、創(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)建局部嵌套類的對象
val ns = NestedSub()
ns.a = 5
ns.b = 8
println("NestedSub對象的 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è)非常“雞肋”的語法训貌,在實(shí)際開發(fā)中很少定義局部嵌套類,這是因?yàn)榫植壳短最惖淖饔糜蛱×嗣扒希荒茉诋?dāng)前方法中使用递沪。
匿名內(nèi)部類
Java 有一個(gè)非常實(shí)用的功能:匿名內(nèi)部類, Kotlin 則徹底拋棄了這個(gè)功能综液。不過讀者不用擔(dān)心款慨, Kotlin 提供了一個(gè)更加強(qiáng)大的語法 : 對象表達(dá)式。
此外谬莹,如果對象是函數(shù)式接口(只包含一個(gè)抽象方法的接口)的實(shí)例檩奠,則可使用帶接口類型前綴的 Lambda 表達(dá)式創(chuàng)建它桩了。 例如,如下代碼創(chuàng)建了一個(gè) Runnable 實(shí)例來啟動線程埠戳。
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}")
}
}
//啟動新線程
Thread(t).start()
//主線程的循環(huán)
for (i in 1..100) {
println("${Thread.currentThread().getName()},i: ${i}")
}
}
上面程序中代碼使用 Lambda 表達(dá)式創(chuàng)建了一個(gè) Runnable 實(shí)例,通過 Runnable 實(shí)例可啟動多個(gè)線程 。
對象表達(dá)式其實(shí)就是增強(qiáng)版的匿名內(nèi)部類探膊,下面會詳細(xì)介紹拷获。
對象表達(dá)式和對象聲明
Kotlin 提供了比匿名內(nèi)部類更加強(qiáng)大的語法:對象表達(dá)式。它們的主要區(qū)別在于:匿名內(nèi)部類只能指定一個(gè)父類型(接口或父類)屁使,但對象表達(dá)式可指定 0~N個(gè)父類型(接口或父類)在岂。
對象表達(dá)式
對象表達(dá)式的語法格式如下:
object[: 0~N 個(gè)父類型] {
//對象表達(dá)式的類體部分
}
從上面語法格式可以看出,對象表達(dá)式可以指定0~N個(gè)父類型(類或接口)蛮寂,對象表達(dá)式的本質(zhì)就是增強(qiáng)版的匿名內(nèi)部類蔽午,因此編譯器處理對象表達(dá)式時(shí)也會像匿名內(nèi)部類一樣生成對應(yīng)的class文件。
關(guān)于對象表達(dá)式還有如下規(guī)則酬蹋。
- 對象表達(dá)式不能是抽象類及老,因?yàn)橄到y(tǒng)在創(chuàng)建對象表達(dá)式時(shí)會立即創(chuàng)建對象。因此不允許將對象表達(dá)式定義成抽象類除嘹。
- 對象表達(dá)式不能定義構(gòu)造器写半。但對象表達(dá)式可以定義初始化塊,可以通過初始化塊來完成構(gòu)造器需要完成的事情尉咕。
- 對象表達(dá)式可以包含內(nèi)部類(有 inner修飾的內(nèi)部類)叠蝇,不能包含嵌套類。
下面程序示范了幾種對象表達(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è)父類型(接口)的對象表達(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è)父類型的對象表達(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è)父類型的對象表達(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ī)单芜,支持自動雙面打印!")
}
}
println(ob3.name)
ob3.output("Kotlin 真不錯(cuò)!")
ob3.printinfo()
}
上面代碼的第一個(gè)對象表達(dá)式只有一個(gè)父類型(接口)蜕该,這個(gè)對象表達(dá)式與Java 的匿名內(nèi)部類實(shí)現(xiàn)太像了,除了 Kotlin 的對象表達(dá)式需要使用 object 關(guān)鍵字洲鸠,該對象表達(dá)式定義的匿名類實(shí)現(xiàn)了 Outputable接口堂淡,因此在類體部分實(shí)現(xiàn)了該接口中的抽象方法。
從上面介紹可以看出扒腕, Kotlin 的對象表達(dá)式可以完全取代 Java 的匿名內(nèi)部類 绢淀。
上面代碼的第二個(gè)對象表達(dá)式則沒有指定父類型,這就是對象表達(dá)式的增強(qiáng)之處瘾腰。我們在該對象表達(dá)式的類體部分定義了初始化塊(程序創(chuàng)建對象時(shí)會自動執(zhí)行)皆的、屬性、方法蹋盆、內(nèi)部類费薄,由此可見硝全,對象表達(dá)式的本質(zhì)其實(shí)就是增強(qiáng)版的匿名內(nèi)部類,因此在類體部 除不能定義構(gòu)造器楞抡、嵌套類之外伟众,完全可以定義其他成員。
上面代碼的第三個(gè)對象表達(dá)式指定了兩個(gè)父類型拌倍,一個(gè)是接口赂鲤,一個(gè)是抽象類,這也是對象表達(dá)式的增強(qiáng)之處柱恤。此處需要說明的是数初,就像 Java 的匿名內(nèi)部類繼承抽象類時(shí),必須調(diào)用抽象類的指定構(gòu)造器一樣梗顺, Kotlin也是如此泡孩,所以我們使用了Product(28.8),表 明調(diào)用Product 抽象類的帶一個(gè) Double 參數(shù)的構(gòu)造器 寺谤。該對象表達(dá)式的類體部分沒有任何特別之處仑鸥,它只是重寫了接口、抽象父類的抽象成員变屁。
運(yùn)行上面代碼眼俊,仔細(xì)查看輸出,應(yīng)該會發(fā)現(xiàn)一個(gè)特征: ob2 對象沒有繼承任何父類型粟关,程序?yàn)樵搶ο?相當(dāng)于匿名內(nèi)部類)定義了 name 屬性和 test()方法疮胖,接下來程序居然可以直接調(diào)用該對象的 name 屬性和 test()方法一一這對 Java 的匿名內(nèi)部類來說是不可想象的 : Java 為匿名內(nèi)部類增加的方法幾乎無法直接訪問,因?yàn)榫幾g器只會把匿名內(nèi)部類當(dāng)成它所繼承的父類或所實(shí)現(xiàn)的接口處理闷板。
但 Kotlin 的對象表達(dá)式不同澎灸, Kotlin 的對象表達(dá)式可分為兩種情形 :
- 對象表達(dá)式在方法(或函數(shù))的局部范圍內(nèi),或使用 private修飾的對象表達(dá)式遮晚, Kotlin編譯器可識別該對象表達(dá)式的真實(shí)類型性昭,就像上面的代碼所示:程序?yàn)?ob2 增加了方法和屬性,在 main()函數(shù)的局部范圍內(nèi)县遣, Kotlin編譯器完全可以識別ob2的真實(shí)類型糜颠, 因此 ob2可調(diào)用對象表達(dá)式增加的屬性和方法。
- 非private修飾的對象表達(dá)式與Java的匿名內(nèi)部類相似萧求,編譯器只會把對象表達(dá)式當(dāng)成它所繼承的父類或所實(shí)現(xiàn)的接口處理括蝠。如果它沒有父類型,系統(tǒng)當(dāng)它是 Any 類型饭聚。
如下程序示范了 Kotlin編譯器處理對象表達(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對象表達(dá)式,編譯器可識別它的真實(shí)類型
//下面代碼正確
println(ob1.name)
//ob2是非private對象表達(dá)式搁拙,編譯器當(dāng)它是Any類型
//下面代碼錯(cuò)誤
//println(ob2.name)
//privateBar private 函數(shù)秒梳,編譯器可識別它返回的對象表達(dá)式的真實(shí)類型
//下面代碼正確
println(privateBar().name)
//publicBar是非private函數(shù)法绵,編譯器將它返回的對象表達(dá)式當(dāng)成Any類型
//下面代碼錯(cuò)誤
//println(publicBar().name)
}
}
fun main(args: Array<String>) {
ObjectExprType().test()
}
從上面代碼可以看出, Kotlin 編譯器可識別 private 對象表達(dá)式的真實(shí)類型酪碘。
此外朋譬, Kotlin 的對象表達(dá)式可訪問或修改其作用域內(nèi)的局部變量(java只能訪問其所在范圍內(nèi)的 effectively final局部變量)。
例如如下程序 :
fun main(args: Array<String>) {
var a = 10
var obj = object {
fun change(){
a++
}
}
obj.change()
println("a的值:${a}")
}
總結(jié)起來兴垦, Kotlin的對象表達(dá)式比 Java的匿名內(nèi)部類增強(qiáng)了三個(gè)方面徙赢。
- 對象表達(dá)式可指定多個(gè)父類型。
- Kotlin編譯器能更準(zhǔn)確地識別局部范圍內(nèi)或 private對象表達(dá)式的類型探越。
- 對象表達(dá)式可訪問或修改其所在范圍內(nèi)的局部變量狡赐。
對象聲明和單例模式
對象聲明的語法格式如下:
object ObjectName[: 0~N個(gè)父類型]{
//對象聲明的類體部分
}
從上面語法格式可以看出,對象聲明與對象表達(dá)式的語法非常相似钦幔,似乎它們之間唯一的區(qū)別是對象表達(dá)式在object關(guān)鍵字后沒有名字 ;而對象聲明需要在 object關(guān)鍵字后指定名字枕屉。
實(shí)際上,對象聲明和對象表達(dá)式還存在如下區(qū)別:
- 對象表達(dá)式是一個(gè)表達(dá)式鲤氢,因此它可以被賦值給變量 ; 而對象聲明不是表達(dá)式搀擂,因此它不能用于賦值。
- 對象聲明可包含嵌套類卷玉,不能包含內(nèi)部類 ; 而對象表達(dá)式可包含內(nèi)部類哨颂,不能包含嵌套類。
- 對象聲明不能定義在函數(shù)和方法內(nèi) ; 但對象表達(dá)式可嵌套在其他對象聲明或非內(nèi)部類中相种。
下面我們用對象聲明來改寫上面的代碼:
interface Outputable {
fun output(msg: String)
}
abstract class Product(var price: Double) {
abstract val name: String
abstract fun printinfo()
}
//指定一個(gè)父類型(接口)的對象聲明
object MyObject1 : Outputable {
override fun output(msg: String) {
//重寫父接口中的抽象方法
for (i in 1..6) {
println("<h${i}>${msg}</h${i}>")
}
}
}
//指定0個(gè)父類型的對象聲明
object MyObject2 {
//初始化塊
init {
println("初始化塊")
}
//屬性
var name = "kotlin"
//方法
fun test() {
println("test()方法")
}
//只能包含嵌套類威恼,不能包含內(nèi)部類
class Foo
}
//指定兩個(gè)父類型的對象聲明
//由于 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ī)蚂子,支持自動雙面打印!")
}
}
fun main(args: Array<String>) {
MyObject1.output("java")
println("------------")
println(MyObject2.name)
MyObject2.test()
println("------------")
println(MyObject3.name)
MyObject3.output("Kotlin 真不錯(cuò)!")
MyObject3.printinfo()
}
將上面程序與上面的對象表達(dá)式的第一個(gè)程序進(jìn)行對比沃测,即可發(fā)現(xiàn)對象聲明不能用于賦值,對象聲明本身己有名稱食茎,因此可以使用對象聲明的名稱訪問該對象蒂破。此外,程序還將對象聲明移到 main()函數(shù)之外别渔,這是因?yàn)閷ο舐暶鞑辉试S放在函數(shù)或方法內(nèi)附迷。
對象聲明專門用于實(shí)現(xiàn)單例模式,對象聲明所定義的對象也就是該類的唯一實(shí)例哎媚,程序可通過對象聲明的名稱直接訪問該類的唯一實(shí)例喇伯。
伴生對象和靜態(tài)成員
在類中定義的對象聲明,可使用 companion修飾拨与,這樣該對象就變成了伴生對象稻据。
每個(gè)類最多只能定義一個(gè)伴生對象,伴生對象相當(dāng)于外部類的對象买喧,程序可通過外部類直接調(diào)用伴生對象的成員捻悯。例如如下代碼:
interface Outputable {
fun output(msg: String)
}
class MyClass {
//使用 companion 修飾的伴生對象
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>) {
//使用伴生對象所在的類調(diào)用伴生對象的方法
MyClass.output("kotlin")
println(MyClass.name)
}
上面代碼使用 companion修飾了對象聲明匆赃,因此該對象變成了伴生對象。接下來在主程序中即可使用伴生對象所在的類調(diào)用伴生對象的成員今缚。
從上面的 MyClass.output("kotlin")代碼不難發(fā)現(xiàn): 這不就是 Java使用類訪問靜態(tài)成員的語法嗎? 實(shí)際上確實(shí)如此算柳,由于Kotlin取消了static關(guān)鍵字,因此Kotlin引入伴生對象來彌補(bǔ)沒有靜態(tài)成員的不足姓言∷蚕睿可見,伴生對象的主要作用就是為其所在的外部類模擬靜態(tài)成員何荚。
從代碼可以看出囱淋,伴生對象的名稱并不重要,因此伴生對象可以省略名稱兽泣。省略名稱之后绎橘,如果程序真的要訪問伴生對象,則可通過 Companion 名稱進(jìn)行訪問唠倦。例如如下代碼称鳞。
interface Outputable {
fun output(msg: String)
}
class MyClass {
//省略名字的伴生對象
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>) {
//使用伴生對象所在的類調(diào)用伴生對象的方法
MyClass.output("kotlin")
//使用 Companion 名稱訪問伴生對象
println(MyClass.Companion)
}
雖然伴生對象的主要作用就是為它所在的類模擬靜態(tài)成員,但只是模擬稠鼻,伴生對象的成員依然是伴生對象本身的實(shí)例成員冈止,并不屬于伴生對象所在的外部類。
在JVM平臺上候齿,可通過@JvmStatic注解讓系統(tǒng)根據(jù)伴生對象的成員為其所在的外部類生成真正的靜態(tài)成員熙暴。具體信息可參考后面的Kotlin與Java互相調(diào)用。
伴生對象的擴(kuò)展
伴生對象也可以被擴(kuò)展慌盯。如果一個(gè)類具有伴生對象周霉,則Kotlin允許為伴生對象擴(kuò)展方法和屬性。為伴生對象擴(kuò)展的方法和屬性亚皂,就相當(dāng)于為伴生對象所在的外部類擴(kuò)展了靜態(tài)成員俱箱,可通過外部類的類名訪問這些擴(kuò)展成員。
如下程序示范了為伴生對象擴(kuò)展成員:
interface Outputable {
fun output(msg: String)
}
class MyClass {
//省略名字的伴生對象
companion object : Outputable {
val name = "name屬性值"
override fun output(msg: String) {
for (i in 1..6) {
println("<h${i}>${msg}</h${i}>")
}
}
}
}
//為伴生對象擴(kuò)展方法
fun MyClass.Companion.test(){
println("為伴生對象擴(kuò)展的方法")
}
val MyClass.Companion.foo
get() = "為伴生對象擴(kuò)展的屬性"
fun main(args: Array<String>) {
//使用伴生對象所在的類調(diào)用伴生對象的方法
MyClass.output("kotlin")
//使用 Companion 名稱訪問伴生對象
println(MyClass.Companion)
//通過伴生對象所在的類調(diào)用為伴生對象擴(kuò)展的成員
MyClass.test()
println(MyClass.foo)
}
上面代碼示范了為伴生對象擴(kuò)展方法和屬性灭必,正如前面所提到的狞谱,Kotlin 可通過Companion名稱訪問伴生對象,因此代碼顯式指定為 MyClass.Companion 擴(kuò)展方法和屬性禁漓,這就是為伴生對象擴(kuò)展方法和屬性跟衅。
枚舉類
Kotlin 當(dāng)然也支持枚舉類, Kotlin 的枚舉類與 Java 差別不大播歼。
枚舉類入門
Kotlin使用 enum class關(guān)鍵字組合定義枚舉類伶跷。枚舉類是一種特殊的類,它一樣可以有自己的屬性、方法撩穿,可以實(shí)現(xiàn)一個(gè)或多個(gè)接口磷支,也可以定義自己的構(gòu)造器。
但枚舉類與普通類有如下簡單區(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訪問控制符,如果省略了構(gòu)造器的訪問控制符悼院,則默認(rèn)使用 private 修飾;如果強(qiáng)制指定訪問控制符伤为,則只能指定 private 修飾符。
- 枚舉類的所有實(shí)例必須在枚舉類的第一行顯式列出据途,否則這個(gè)枚舉類永遠(yuǎn)都不能產(chǎn)生實(shí)例绞愚。列出枚舉實(shí)例后最好用分號結(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 文件,這表明枚舉類是一種特殊的類 俺榆。 由此可見感昼, enum class 關(guān)鍵字組合和 class、 interface 關(guān)鍵字的作用大致相似 肋演。
在定義枚舉類時(shí)抑诸,需要顯式列出所有的枚舉值,如上面的 SPRING, SUMMER, FALL, WINTER 所示爹殊,所有的枚舉值之間以英文逗號(,)隔開蜕乡。這些枚舉值代表了該枚舉類的所有可能的實(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)
//直接訪問枚舉值
println(Season.WINTER)
}
上面程序測試了 Season 枚舉類的用法辛块,該類通過 values()方法返回了 Season 枚舉類的所有實(shí)例畔派,并通過循環(huán)迭代輸出了 Season 枚舉類的所有實(shí)例;還使用了 Season 的 valueOf()方法根據(jù)字符串參數(shù)來獲取枚舉值。
前面己經(jīng)介紹過润绵,所有的枚舉類都繼承了 kotlin.Enum類线椰,所以枚舉類可以直接使用該類中所包含的屬性和方法。 kotlin.Enum 類中提供了如下屬性和方法尘盼。
- name 屬性: 返回此枚舉實(shí)例的名稱憨愉,這個(gè)名稱就是定義枚舉類時(shí)列出的所有枚舉值之一。與此屬性相比卿捎,應(yīng)該優(yōu)先考慮使用 toString()方法配紫,因?yàn)?toString() 方法可返回對用戶更加友好的名稱。
- ordinal 屬性: 返回枚舉值在枚舉類中的索引值(就是枚舉值在枚舉聲明中的位置午阵,第一個(gè)枚舉值的索引值為 0) 躺孝。
- int compareTo(E o): 該方法用于與指定的枚舉對象比較順序,同 一個(gè)枚舉實(shí)例只能與相同類型的枚舉實(shí)例進(jìn)行比較底桂。如果該枚舉對象位于指定的枚舉對象之后植袍,則返回正整數(shù);如果該枚舉對象位于指定的枚舉對象之前,則返回負(fù)整數(shù) ; 否則返回 0戚啥。
- String toString(): 返回枚舉常量的名稱奋单,與 name 屬性相似,但 toString()方法更常用猫十。
正如前面所看到的览濒,當(dāng)程序使用 println(s)語句來打印枚舉值時(shí),實(shí)際上輸出的是該枚舉值toString()方法的返回值拖云,也就是輸出該枚舉值的名字贷笛。
枚舉類的屬性、方法和構(gòu)造器
枚舉類也是一種類宙项,只是一種比較特殊的類乏苦,因此它一樣可以定義屬性、方法和構(gòu)造器尤筐。由于枚舉類應(yīng)該設(shè)計(jì)成不可變類汇荐,因此它的屬性值不允許改變,這樣會更安全盆繁。 Kotlin禁止開發(fā)者對屬性賦值掀淘,并推薦使用 val 為枚舉聲明只讀屬性。 由于枚舉的屬性都是只讀屬性油昂,枚舉必須在構(gòu)造器中為這些屬性指定初始值(或在初始化塊中指定初始值革娄, 一般不會在定義時(shí)指定初始值倾贰,因?yàn)檫@樣會導(dǎo)致所有枚舉值的該屬性值總是相同的),因此應(yīng)該為枚舉類顯式定義帶參數(shù)的構(gòu)造器拦惋。
一旦為枚舉類顯式定義了帶參數(shù)的構(gòu)造器匆浙,在列出枚舉值時(shí)就必須對應(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í)就必須對應(yīng)地傳入?yún)?shù)叹放。也就是說饰恕,在枚舉類中列出枚舉值時(shí),實(shí)際上就是調(diào)用構(gòu)造器創(chuàng)建枚舉類對象井仰,只是這里無須顯式調(diào)用構(gòu)造器 。前面列出枚舉值時(shí)無須傳入?yún)?shù)破加,甚至無須使用括號俱恶,僅僅是因?yàn)榍懊娴拿杜e類包含無參數(shù)的構(gòu)造器。
不難看出范舀,上面程序中的粗體字代碼實(shí)際上等同于如下代碼:
MALE = new Gender ("男" ) , FEMALE = new Gender ("女");
此外合是,由于上面的枚舉類需要在列出枚舉值之后定義額外的枚舉成員(如枚舉方法),因此上面程序需要用分號表示枚舉值列表結(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>) {
//通過 Gender 的 valueOf ()方法根據(jù)枚舉名獲取枚舉值
val g = Gender.valueOf("FEMALE")
//訪問枚舉值的 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)接口不過如此蛾茉,與普通類實(shí)現(xiàn)接口完全一樣:同樣要將被實(shí)現(xiàn)的接口放在英文冒號后面,并實(shí)現(xiàn)接口中包含的抽象方法 撩鹿。
包含抽象方法的抽象枚舉類
假設(shè)有一個(gè) Operation枚舉類谦炬,它的 4個(gè)枚舉值 PLUS,MINUS,TIMES, DIVIDE分別代表加、減节沦、乘键思、除 4 種運(yùn)算,該枚舉類需要定義一個(gè) eval()方法來完成計(jì)算甫贯。 從上面描述可以看出Operation 需要讓PLUS,MINUS,TIMES, DIVIDE這 4 個(gè)值對 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))
}
編譯上面程序失乾,會生成 5 個(gè) class 文件,其中 Operation 對應(yīng)一個(gè) class 文件纬乍,它的4個(gè)匿名內(nèi)部子類各對應(yīng)一個(gè) class文件碱茁。
枚舉類中包含抽象方法時(shí)依然不能使用 abstract 修飾枚舉類(因?yàn)橄到y(tǒng)自動會為它添加 abstract 關(guān)鍵字),但因?yàn)槊杜e類需要顯式創(chuàng)建枚舉值仿贬,而不是作為父類纽竣,所以在定義每個(gè)枚舉值時(shí)必須為抽象成員提供實(shí)現(xiàn),否則將出現(xiàn)編譯錯(cuò)誤茧泪。
上面程序中的代碼看起來有些奇怪:當(dāng)創(chuàng)建 PLUS,MINUS,TIMES, DIVIDE 枚舉值時(shí)蜓氨,后面又緊跟了一對花括號,這對花括號中包含了一個(gè) eval()方法定義队伟。如果讀者還記得對象表達(dá)式(或匿名內(nèi)部類)語法的話穴吹,則可能就熟悉這樣的語法了,花括號部分實(shí)際上 就是一個(gè)類體部分嗜侮,在這種情況下港令,當(dāng)創(chuàng)建PLUS,MINUS,TIMES, DIVIDE枚舉值時(shí) , 并不是直接創(chuàng)建 Operation 枚舉類的實(shí)例的锈颗,而是相當(dāng)于創(chuàng)建 Operation的匿名子類的實(shí)例顷霹。因?yàn)榇a的括號部分實(shí)際上是對象表達(dá)式(匿名內(nèi)部類)的類體部分,所以這個(gè)部分的代碼語法與前面介紹的對象表達(dá)式(匿名內(nèi)部類)的語法大致相似击吱。
類委托和屬性委托
委托是 Kotlin 的另一個(gè)特色功能淋淀,也是Java原本所不具備的功能。Kotlin的委托可分為類委托和屬性委托覆醇。
類委托
類委托是代理模式的應(yīng)用朵纷,而代理模式可作為繼承的一個(gè)不錯(cuò)的替代。類委托的本質(zhì)就是將本類需要實(shí)現(xiàn)的部分方法委托給其他對象叫乌,相當(dāng)于借用其他對象的方法作為自己的實(shí)現(xiàn) 柴罐。
例如,下面定義一個(gè)類憨奸,該類實(shí)現(xiàn)了一個(gè)接口革屠,但該類并不實(shí)現(xiàn)該接口中的抽象方法,而是借用其他對象中的方法來實(shí)現(xiàn)排宰,被借用的對象被稱為被委托對象似芝。
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作為委托對象
class Printer(b:DefaultOutput) :Outputable by b
//定義 Projector 類板甘,指定新建的對象作為委托對象
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 對象的委托對象是 output
var printer = Printer(output)
//其實(shí)就是調(diào)用委托對象的 output ()方法
printer.output("xq")
//Projector 對象的委托對象也是 output
var p = Projector()
//Projector 本身重寫了 output ()方法党瓮,所以此處是調(diào)用本類重寫的方法
p.output("xy")
//其實(shí)就是調(diào)用委托對象的 type 屬性方法
println(p.type)
}
上面程序定義了 Printer 和 Projector 兩個(gè)類,這兩個(gè)類都實(shí)現(xiàn)了 Ouputable 接口盐类,因此需要實(shí)現(xiàn)該接口中的抽象方法和抽象屬性寞奸。程序通過 by關(guān)鍵字為這兩個(gè)類指定了委托對象呛谜,這 意味著這兩個(gè)類可直接“借用”被委托對象所實(shí)現(xiàn)的方法和屬性。
上面程序中 Printer類雖然沒有實(shí)現(xiàn) output()方法和 type屬性枪萄,但它的實(shí)例 一樣可以在 main() 函數(shù)中調(diào)用 output()方法和 type 屬性 隐岛。 Projector類雖然指定了 DefaultOutput對象作為委托,但 由于該類本身也實(shí)現(xiàn)了 output()方法瓷翻,因此當(dāng) Projector 對象調(diào)用 output()方法時(shí)聚凹,它不再需要調(diào)用委托對象的 output()方法,而是直接使用自己實(shí)現(xiàn)的方法齐帚。
由此可見妒牙,當(dāng)一個(gè)類重寫了委托對象所包含的方法之后, Kotlin 將優(yōu)先使用該類自己實(shí)現(xiàn)的方法对妄。
上面程序示范了為類指定委托對象的兩種方式湘今,第一種方式是通過構(gòu)造器參數(shù)指定委托對 象;第二種方式是直接在類定義的 by后新建對象。通常會采用第一種方式剪菱, 因?yàn)檫@樣可以讓多個(gè)對象共享同一個(gè)委托對象象浑。
屬性委托
Kotlin 也支持屬性委托,屬性委托可以將多個(gè)類的類似屬性統(tǒng)一交給委托對象集中實(shí)現(xiàn)琅豆,這樣就可避免每個(gè)類都需要單獨(dú)實(shí)現(xiàn)這些屬性。
對于指定了委托對象的屬性而言篓吁,由于它的實(shí)現(xiàn)邏輯己經(jīng)交給委托對象處理茫因,因此開發(fā)者不能為委托屬性提供 getter 和 setter 方法, Kotlin也不會為委托屬性提供 getter杖剪、 setter方法的默認(rèn)實(shí)現(xiàn)冻押。
一旦將某個(gè)對象指定為屬性的委托對象,該對象就會全面接管該屬性的讀取( getter)和寫入( setter)操作盛嘿,因此屬性的委托對象無須實(shí)現(xiàn)任何接口洛巢,但一定要提供一個(gè) getValue()方法和 setValue()方法(val 屬性無須提供 setValue方法)。
對于只讀屬性(用 val 聲明的屬性)而言次兆,由于程序無須設(shè)置屬性值稿茉,只要能讀取屬性值即可,因此只讀屬性的委托對象只需提供使用 operator修飾的 getValue()方法芥炭。該方法的參數(shù)和返回值要求如下漓库。
- thisRef:該參數(shù)代表屬性所屬的對象,因此該參數(shù)的類型必須是屬性所屬對象的類型 (對于擴(kuò)展屬性园蝠,指被擴(kuò)展的類型)或其超類型渺蒿。
- property:該參數(shù)代表目標(biāo)屬性,該參數(shù)的類型必須是 KPropetry<*>或其超類型 彪薛。
- 返回值: 該方法必須返回與目標(biāo)屬性相同的類型(或其子類型)茂装。
對于讀寫屬性(用 var聲明的屬性)而言怠蹂,屬性的委托對象必須額外提供一個(gè)使用 operator修飾的 setValue()方法,該方法不需要返回值少态,但必須指定如下參數(shù)城侧。
- thisRef: 與 getValue()的 thisRef參數(shù)的作用相同。
- property: 與 getValue()的property參數(shù)的作用相同况增。
- newValue: 該參數(shù)代表目標(biāo)屬性新設(shè)置的屬性值赞庶,該參數(shù)的類型必須具有和目標(biāo)屬性相同的類型或其超類型 。
屬性的委托對象的 getValue()澳骤、setValue()方法既可以是成員方法歧强,也可以是擴(kuò)展方法∥梗總之摊册,只要是符合規(guī)則的兩個(gè)方法均可。
Kotlin標(biāo)準(zhǔn)庫在kotlin.properties下提供了 ReadOnlyProperty和 ReadWriteProperty兩個(gè)接口颊艳,其中ReadOnlyProperty接口定義了一個(gè)符合只讀屬性委托標(biāo)準(zhǔn)的getValue()抽象方法茅特,因此該接口的實(shí)現(xiàn)類可作為只讀屬性的委托對象;而ReadWriteProperty接口定義了符合讀寫屬性委托標(biāo)準(zhǔn)的 getValue()和 setValue()抽象方法,因此該接口的實(shí)現(xiàn)類可作為讀寫屬性的委托對象棋枕。
import kotlin.reflect.KProperty
class PropertyDelegation {
//該屬性的委托對象是 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)用屬性的委托對象的 getter 方法
println(pd.name)
//寫入屬性,實(shí)際上是調(diào)用屬性的委托對象的 setter 方法
pd.name = "kotlin"
println(pd.name)
}
上面代碼將PropertyDelegation的 name 屬性委托給 MyDelegation 對象重斑。接 下來程序定義了 MyDelegation 類兵睛,由于該類的對象要作為讀寫屬性的委托對象,因此該類必須實(shí)現(xiàn)符合要求的 getter窥浪、 setter方法祖很,如 MyDelegation中代碼所示。
當(dāng)程序讀取 PropertyDelegation對象的 name 屬性時(shí)漾脂,實(shí)際上就是執(zhí)行 MyDelegation對象所提供的 getValue()方法;當(dāng)程序?qū)?PropertyDelegation對象的 name 屬性賦值時(shí)假颇,實(shí)際上就是執(zhí)行 MyDelegation對象所提供的setValue()方法。
實(shí)際上骨稿,上面的MyDelegation類已經(jīng)實(shí)現(xiàn)了ReadWriteProperty接口的規(guī)范笨鸡,因此程序完全可讓該類實(shí)現(xiàn) ReadWriteProperty < PropertyDelegation, String>接口,再為getValue()和setValue()方法添加override修飾符。
前面己經(jīng)講過, Kotlin不會為委托屬性生成幕后字段蒲牧、 getter和 setter方法,這是不言而喻的趟脂,因此委托屬性的所有處理細(xì)節(jié)都由委托對象負(fù)責(zé) 。Kotlin會委托屬性生成輔助屬性例衍,該輔助屬性引用了屬性的委托對象昔期。當(dāng)程序調(diào)用委托屬性的 getter已卸、 setter方法時(shí),實(shí)際上就是執(zhí)行委托對象的 getValue()硼一、 setValue()方法累澡。例如,我們定義了如下委托屬性 :
class C{
var prop: Type by MyDelegation()
}
上面代碼定義了名為prop的委托屬性般贼,該屬性的委托對象是MyDelegate愧哟。上面代碼被編譯器處理之后將生成如下代碼 :
//這段代碼是由編譯器生成的
class C {
//生成隱藏屬性 ,隱藏屬 性引用代理對象
private val prop$delegate = MyDelegation()
var prop: Type
// getter 方法哼蛆,實(shí)際上就是調(diào)用代理對象的 getValue ()方法
get() = prop$delegate.getValue(this, this::prop)
//setter 方法蕊梧,實(shí)際上就是調(diào)用代理對象的 setValue ()方法
set(value:Type) {
prop$delegate.setValue(this, this::prop,value)
}
}
在上面編譯器生成的 getter、 setter 方法實(shí)現(xiàn)中腮介,其實(shí)就是調(diào)用代理對象的 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>對象 十艾。
Lazy<T>對象包含了一個(gè)符合只讀屬性委托要求的 getValue()方法,因此該 Lazy<T>對象可作為只讀屬性的委托對象 腾节。Lazy<T>對象只能作為只讀屬性的委托對象 疟羹。
val lazyProp: String by lazy {
println("第一次訪問時(shí)執(zhí)行代碼塊")
"kotlin"
}
fun main(args: Array<String>) {
//兩次訪問 lazyProp 屬性
println(lazyProp)
println(lazyProp)
}
上面代碼將 lazyProp 屬性的委托對象指定為 lazy()函數(shù)的返回值: Lazy<T> 對象。這樣當(dāng)程序訪問 lazyProp 屬性時(shí)禀倔,實(shí)際上就是調(diào)用 Lazy<T>對象的 getValue()方法。
Lazy<T>的 getValue()方法的處理邏輯是:第一次調(diào)用該方法時(shí)参淫,程序會計(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í)會提供自動添加保證線程安全的同步鎖棕兼,因此開銷略大陡舅。
上面第一個(gè)lazy()函數(shù)相當(dāng)于第二個(gè)lazy()函數(shù)的第一個(gè)參數(shù)指定為LazyThreadSafetyMode.SYNCHRONIZED
如果程序不需要 lazy()函數(shù)保證線程安全,希望提供更好的性能伴挚,則可將 lazy()函數(shù)的第一個(gè)參數(shù)指定為如下兩種模式靶衍。
- LazyThreadSafetyMode.PUBLICATION : 在這種模式下灾炭,lazy()函數(shù)不會使用排他同步鎖,多個(gè)線程可同時(shí)執(zhí)行颅眶。
- LazyThreadSafetyMode.NONE: 在這種模式下蜈出,lazy()函數(shù)不會有任何線程安全相關(guān)的操作和開銷 。
例如涛酗,將上面的 lazyProp 改為如下形式來取消線程安全保證铡原,從而獲得最佳性能
val lazyProp: String by lazy (LazyThreadSafetyMode.NONE){
println("第一次訪問時(shí)執(zhí)行代碼塊")
"kotlin"
}
屬性監(jiān)聽
Kotlin的kotlin.properties包下提供了一個(gè)Delegates對象聲明 (單例對象),該對象中包含如下兩個(gè)常用方法 商叹。
- fun<T> observable(initialValue: T, onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Unit): ReadWriteProperty<Any?, T>: 該方法返回 一個(gè)ReadWriteProperty對象燕刻,因此該方法的返回值可作為讀寫屬性的委托對象。該方法的第一個(gè)參數(shù)用于為接受委托的屬性指定初始值沈自,第二個(gè)參數(shù)可接受 Lambda 表達(dá)式酌儒, 當(dāng)接受委托的屬性被設(shè)置值時(shí),該 Lambda表達(dá)式就會被觸發(fā) 枯途。
- fun <T> vetoable(initialValue: T, onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Boolean): ReadWritePrope<Any?, T>: 該方法與前一個(gè)方法基本相同忌怎, 區(qū)別只是負(fù)責(zé)執(zhí)行監(jiān)聽的 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)聽的屬性
//第二個(gè)參數(shù)代表修改之前的值
//第三個(gè)參數(shù)代表被設(shè)置的新值
prop, old, new ->
println("${prop}的${old}被改為${new}")
}
fun main(args: Array<String>) {
//訪問 observableProp 屬性 晚岭, 不會觸發(fā)監(jiān)聽器的 Lambda 表達(dá)式
println(observableProp)
//設(shè)置屬性值鸥印,觸發(fā)監(jiān)聽器的 Lambda 表達(dá)式
observableProp ="kotlin"
println(observableProp)
}
從上面的運(yùn)行結(jié)果可以看出,無論我們?yōu)閷傩栽O(shè)置的新值是什么坦报,總可以設(shè)置成功库说。如果需要對新值進(jìn)行判斷,然后再確定是否設(shè)置成功片择,則可使用 Delegates 對象的 vetoable()方法 潜的。 例如如下代碼:
import kotlin.properties.Delegates
var observableProp: Int by Delegates.vetoable(20) {
//Lambda表達(dá)式的第一個(gè)參數(shù)代表被監(jiān)聽的屬性
//第二個(gè)參數(shù)代表修改之前的值
//第三個(gè)參數(shù)代表被設(shè)置的新值
prop, old, new ->
println("${prop}的${old}被改為${new}")
new > old
}
fun main(args: Array<String>) {
//訪問 observableProp 屬性 , 不會觸發(fā)監(jiān)聽器的 Lambda 表達(dá)式
println(observableProp)
//新值小于舊值字管,監(jiān)聽的 Lambda 表達(dá)式返回 false啰挪,新值設(shè)置失敗
observableProp = 15
println(observableProp) //輸出 20
//新值大于舊值,監(jiān)聽的 Lambda 表達(dá)式返回 true嘲叔,新值設(shè)置成功
observableProp =25
println(25)
}
上面 Lambda表達(dá)式內(nèi)最后的表達(dá)式就是它的返回值亡呵,如果 new>old,則返回 true; 否則返回 false硫戈。由此可見锰什,此處我們要求新設(shè)置的屬性值必須比原屬性值大才能設(shè)置成功。
現(xiàn)代編程語言幾乎都提供了屬性監(jiān)聽的功能,由于 Java 并沒有真正意義上的 屬性( Java 的屬性等于field歇由、 getter卵牍、 setter方法的組合),因此 Java對屬性的監(jiān)聽需要通過 setter 方法來實(shí)現(xiàn) 沦泌。而 Kotlin 的屬性監(jiān)聽則通過屬性委托機(jī)制來實(shí)現(xiàn)糊昙。
使用Map存儲屬性值
Kotlin 的 Map 提供了如下方法:
operator fun <V, V1 : V> Map<in String, V>.getValue(thisRef: Any?, property:KProperty<*>): V1
該方法符合只讀屬性的委托對象的要求,這意味著 Map 對象可作為只讀對象的委托 谢谦。
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è)方法符合讀寫屬性的委托對象的要求释牺,這意味著 MutableMap對象可作為讀寫對象的委托。
因此程序可將類只讀屬性委托給 Map對象管理回挽,在這種情況下没咙,對象本身并不負(fù)責(zé)存儲對象狀態(tài),而是將對象狀態(tài)保存在 Map 集合中千劈。這么做的好處是祭刚,當(dāng)程序需要與外部接口(如JSON)通信時(shí),程序并不需要將該對象直接暴露出來墙牌,只要將該對象屬性所委托的Map暴露出來即可涡驮,而 Map 則完整地保存了整個(gè)對象狀態(tài)。
例如喜滨,如下程序使用 Map 存儲對象的只讀屬性捉捅。
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("--------------")
//將對象持有的 map 暴露出來,其他程序可通過標(biāo)準(zhǔn) Map 讀取數(shù)據(jù)
val map = item.map
println(map["barCode"])
println(map["name"])
println(map["price"])
}
從上面代碼可以看出虽风,程序可以將 Item對象的所有只讀屬性都委托給一個(gè)Map對象棒口,其實(shí)道理很簡單 :Item對象的每個(gè)屬性名就相當(dāng)于 Map 的 key,屬性值就相當(dāng)于 key對應(yīng)的value辜膝。指定Map 對象作為 Item對象的委托之后无牵,接下來程序只要把 Item 的 Map 暴露出來,程序即可通過該 Map來獲取 Item對象的狀態(tài)厂抖。
如果對象包含的屬性是讀寫屬性合敦,則需要使用 MutableMap作為委托對象 。例如如下程序:
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 對象的屬性验游,其實(shí)會委托給 MutableMap 處理
item.barCode="111"
item.name= "kotlin"
item.price =20.1
//將對象持有的 map 暴露出來,其他程序可通過標(biāo)準(zhǔn) Map 讀取數(shù)據(jù)
val map = item.map
println(map["barCode"])
println(map["name"])
println(map["price"])
}
上面程序?qū)?Mutableltem 的讀寫屬性委托給 MutableMap 對象保檐,這樣當(dāng)程序設(shè)置 Mutableltem對象的屬性值時(shí)耕蝉,實(shí)際上是交給 MutableMap 負(fù)責(zé)處理的,相當(dāng)于為 MutableMap 設(shè)置了key-value對夜只。 因此程序后面可通過 MutableMap獲取到為 Item設(shè)置的屬性值垒在。
反過來,如果程序?yàn)楸晃械?MutableMap 對象設(shè)置了 key-value 對扔亥,實(shí)際上就是修改了Item對象的狀態(tài)场躯。
局部屬性委托
從 Kotlin 1.1 開始谈为, Kotlin 支持為局部變量指定委托對象。這種指定了委托對象的局部變量被稱為“局部委托屬性”踢关,其實(shí)還是局部變量伞鲫,只是對該變量的讀取、賦值操作將會交給委托對象去實(shí)現(xiàn)签舞。
與前面介紹的屬性委托相似 秕脓,局部變量的委托對象同樣要遵守一定的要求。對于只讀屬性而言儒搭,同樣需要實(shí)現(xiàn) operator修飾的 getValue(thisRef: Nothing?, property: !<Property<*>)方法吠架, 注意該方法的第一個(gè)參數(shù)是 Nothing?類型, Nothing 是 Kotlin 提供的 一個(gè)特殊的類搂鲫,專門用于代表永不存在的對象傍药。
由于局部變量不屬于任何對象, 因此它的委托對象的 getValue()方法的第一個(gè)參數(shù)自然也就不存在了魂仍,因此 Kotlin使用 Nothing?來聲明 getValue()方法的第一個(gè)參數(shù)的類型 拐辽。
對于讀寫屬性而言,則需要實(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()
//訪問局部變量 仑氛, 實(shí)際上是調(diào)用 MyDelegation 對象的 getValue()方法
println(name)
//對局部變量賦值乙埃,實(shí)際上是調(diào)用 MyDelegation 對象的 setValue()方法
name = "kotlin"
println(name)
}
從上面代碼可以看出,局部變量的委托對象的 getValue()和setValue()方法的第一個(gè)參數(shù)類型變成了 Nothing?锯岖,其他變化不大介袜。
與普通屬性類似的是,程序當(dāng)然也可以利用 Kotlin所提供的標(biāo)準(zhǔn)委托出吹,比如可以利用 lazy() 函數(shù)對局部變量延遲初始化 遇伞。程序如下:
fun main(args: Array<String>) {
val name :String by lazy{
println("”計(jì)算 name局部變量")
"kotlin"
}
//第一 次訪問 name 屬性時(shí),會執(zhí)行 Lambda 表達(dá)式
println(name)
//以后再次訪問 name 屬性時(shí)捶牢,則直接使用第一次計(jì)算的值
println(name)
}
委托工廠
除提供 getValue()鸠珠、 setValue()方法的對象可作為屬性的委托對象之外,從 Kotlin1.1 開始秋麸, 一種類似于“委托工廠”的對象也可作為委托對象渐排。委托工廠需要提供如下方法。
- operator fun provideDelegate(thisRef: Any?, prop: KProperty<*>): 該方法的兩個(gè)參數(shù)與委托的 getValue()方法的兩個(gè)參數(shù)的意義相同 灸蟆。
如果上面方法返回 ReadOnlyPrope即對象驯耻,那么該對象可作為只讀屬性的委托對象 ; 如果返回 ReadWriteProperty對象,則該對象就可作為讀寫屬性的委托對象。
使用 provideDelegate()方法來生成委托的好處是可缚,Kotlin 會保證對象的屬性被初始化時(shí)調(diào)用該方法來生成委托霎迫,這樣我們即可在該方法中加入任何自定義代碼,完成任何自定義邏輯帘靡。
下面示例示范了“委托工廠”的用法:
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
class MyTarget {
//該屬性的委托對象是 provideDelegate()方法返回的 MyDelegation對象
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í)際的委托對象
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)建對象(初始化屬性),調(diào)用委托工廠的 provideDelegate()方法
var pd =MyTarget()
//讀取屬性 测柠,實(shí)際上是調(diào)用屬性的委托對象的 getter 方法
println(pd.name)
//寫入屬性 炼鞠,實(shí)際上是調(diào)用屬性的委托對象的 setter 方法
pd.name ="java"
println(pd.name)
}
上面代碼指定屬性的委托對象為PropertyChecker,該對象是一個(gè)委托工廠轰胁,它提供了provideDelegate()方法來生成委托谒主,該方法在生成委托之前先調(diào)用 checkProperty()方法執(zhí)行檢查,具體檢查什么赃阀,由開發(fā)者自己決定;然后該方法才返回真正的委托對象: MyDelegation霎肯。 從上面描述可以看出,使用委托工廠的好處在于:程序可以在屬性初始化時(shí)自動回調(diào)委托工廠的 provideDelegate()方法榛斯,而該方法則可以執(zhí)行自己的業(yè)務(wù)操作观游。
屬性的委托依然由 MyDelegation 對象負(fù)責(zé),只是在屬性初始化時(shí)插入了業(yè)務(wù)檢查方法: checkProperty()驮俗。