本篇文章主要介紹以下幾個知識點:
- 泛型的基本用法
- 類委托和委托屬性
- 自定義 lazy 函數
內容參考自第一行代碼第3版
1. 泛型的基本用法
泛型允許在不指定具體類型的情況下進行編程,這樣會有更好的擴展性焚廊。
泛型主要有兩種定義方式:定義泛型類 和 定義泛型方法。使用的語法結構都是<T>
伍派,當然括號內的 T 可以用任何英文字母或單詞。
如定義個泛型類:
class MyClass<T> {
fun method(param: T): T {
return param
}
}
在調用 MyClass
類和 method()
方法時就可以將泛型指定成具體的類型:
fun main() {
// 這邊把泛型指定成 Int 類型
val mClass = MyClass<Int>()
val result = mClass.method(123)
println(result)
}
若不想定義泛型類,只想定義一個泛型方法截粗,可改為:
class MyClass {
fun <T> method(param: T): T {
return param
}
}
此時調用方式也要修改:
fun main() {
val mClass = MyClass()
// val result = mClass.method<Int>(123)
// 由于 Kotlin 的推導機制,這邊的 Int 可以省略
val result = mClass.method(123)
println(result)
}
Kotlin 還允許對泛型類型進行限制鸵隧,通過指定上界的方式對泛型的類型進行約束绸罗,如將設置為 Number 類型:
class MyClass {
fun <T : Number> method(param: T): T {
return param
}
}
注:在默認情況下,泛型的上界是 Any?
豆瘫,即所有的泛型都是可以指定成可空類型的珊蟀,若想讓泛型的類型不可空谓着,手動指定成 Any
即可蘸际。
回顧前面學習高階函數時的一個例子:
// 這個 build 函數和 apply 函數的作用是一樣的勒庄,
// 但 build 函數只能作用在 StringBuilder 中抛腕,而 apply 可以作用在所有類上
fun StringBuilder.build(block: StringBuilder.() -> Unit): StringBuilder {
block()
return this
}
要讓上面的 build
實現和 apply
一樣的功能蓖谢,可使用泛型修改如下:
fun <T> T.build(block: T.() -> Unit): T {
block()
return this
}
這樣就可以和 apply
一樣去使用 build
函數了逗宁。
2. 類委托和委托屬性
委托是一種設計模式倡怎,其基本理念是:操作對象自己不會去處理某段邏輯踢械,而是把工作委托給另外一個輔助對象去處理趟薄。
Kotlin 中將委托功能分為:類委托和委托屬性绽诚。(注:Java 對委托沒有語言層面的實現)
- 類委托:將一個類的具體實現委托給另一個類去完成。
下面舉個栗子,定義一個 MySet
類實現 Set
接口:
// Set 數據結構和 list 不同的是所存儲的數據是無序的恩够,不能存儲重復的數據
// Set 接口卒落,要使用它需要使用它具體的實現類,如 HashSet
// 這邊在構造函數中接收了一個 HashSet 參數蜂桶,相當于一個輔助對象
// 然后在 Set 接口中所有的方法實現中儡毕,都沒有進行自己的實現,
// 而是調用了輔助對象 HashSet 中相應的方法扑媚,這其實就是一種委托模式
class MySet<T>(val helperSet: HashSet<T>) : Set<T> {
override val size: Int get() = helperSet.size
override fun contains(element: T) = helperSet.contains(element)
override fun containsAll(elements: Collection<T>) = helperSet.containsAll(elements)
override fun isEmpty() = helperSet.isEmpty()
override fun iterator() = helperSet.iterator()
}
委托模式的意義主要在于:大部分的方法調用輔助對象中的方法腰湾,少部分的方法由自己實現,甚至加入一些獨有方法疆股。(如果都調用輔助對象中的方法那還不如直接使用輔助對象得了)
不過费坊,上面的寫法有一定的弊端,當輔助對象中存在很多方法時旬痹,每個都去這樣調用輔助對象中的相應方法實現附井,那就很繁瑣了。還好 Kotlin 中可以通過類委托來解決两残。
Kotlin 中委托使用的關鍵字是 by
永毅,在接口聲明的后面使用 by
關鍵字,再接上受委托的輔助對象即可:
class MySet<T>(val helperSet: HashSet<T>) : Set<T> by helperSet{
// 通過類委托磕昼,這邊可以免去之前的一大堆模板式代碼了卷雕,實現的效果卻是一樣的
}
比如對其某個方法重新實現节猿、新增自己的方法:
class MySet<T>(val helperSet: HashSet<T>) : Set<T> by helperSet {
// 新增一個 sayHello 方法
fun sayHello() = println("Hello Wonderful")
// 重寫 isEmpty 方法
override fun isEmpty() = false
}
- 委托屬性:將一個屬性(字段)的具體實現委托給另一個類去完成票从。
委托屬性的語法結構如下:
class MyClass {
// by 關鍵字左邊 p 屬性的具體實現委托給了 Delegate 類去完成
var p by Delegate()
}
接著還得對 Delegate 類進行具體的實現,如下:
class Delegate {
var propValue: Any? = null
// 需要使用 operator 關鍵字聲明
// KProperty<*> 是 Kotlin 中的一個屬性操作類滨嘱,可用于獲取各種屬性相關的值峰鄙,
// 即使用不著也必須在方法參數上聲明
// <*> 表示不關心泛型的具體類型(類似于 Java 中的<?>)
operator fun getValue(mClass: MyClass, prop: KProperty<*>): Any? {
return propValue
}
operator fun setValue(mClass: MyClass, prop: KProperty<*>, value: Any?) {
propValue = value
}
}
這樣當調用 p 屬性時會自動調用 Delegate 類的 getValue()
方法,當給 p 屬性賦值時會自動調用 Delegate 類的 setValue()
方法太雨。
3. 實現一個自己的 lazy 函數
有時候初始化變量時吟榴,會用一種懶加載技術,把想要延遲執(zhí)行的代碼放到 by lazy
代碼塊中囊扳,這樣代碼塊中的代碼在一開始時就不會執(zhí)行吩翻,只有當變量首次被調用時代碼塊中的代碼才會執(zhí)行。
by lazy
的語法結構如下:
// 這里 by lazy 不是連在一起的锥咸,by 是關鍵字狭瞎,lazy 是一個高階函數
// 在 lazy 函數中會創(chuàng)建并返回一個 Delegate 對象
val p by lazy { ... }
下面舉個栗子來實現一個自己的 lazy
函數:
class Later<T>(val block: () -> T) {
// 這里定義了個 Later 類,并將他指定成泛型類
}
接著在 Later
類中實現 getValue()
方法:
class Later<T>(val block: () -> T) {
var value: Any? = null
operator fun getValue(any: Any?, prop: KProperty<*>): T {
if (value == null) {
value = block()
}
return value as T
}
// 由于懶加載技術不會對屬性進行賦值搏予,無需實現 setValue 方法
}
為了讓它用法更加類似于 lazy 函數熊锭,再定義個頂層函數:
// 用于創(chuàng)建 Later 類的實例,并將接收的函數類型參數傳給 Later 類的構造函數
fun <T> later(block: () -> T) = Later(block)
這樣,自定義的 later 懶加載函數就完成了碗殷,用法如下:
val p by later {
// something to do
}
本篇文章就介紹到這精绎。