一、本文概要
本文是對<<Kotlin in Action>>
的學習筆記,如果需要執行相應的程式碼可以訪問線上環境 try.kotlinlang.org,這部分的思維導圖為:

二、泛型型別引數
泛型允許你定義帶 型別形參 的型別,當這種型別的例項被建立出來的時候,型別形參被替換成為 型別實參 的具體型別。
和Java
不同,Kotlin
始終要求型別實參要麼被顯示地說明,要麼能被編譯器推匯出來。例如,在Java
中可以宣告List
型別的變數,而不需要說明它可以包含哪些事物,而Kotlin
從一開始就有泛型,所以它不支援原生態型別,型別實參必須定義。
2.1 泛型函式和屬性
如果需要編寫一個使用列表的函式,希望它可以在任何列表上使用,需要編寫一個泛型函式,泛型函式有它自己的型別形參,這些 型別形參 在每次呼叫時都必須替換成具體的 型別實參。
2.1.1 slice
例如集合當中slice
函式的定義:
fun <T> List<T>.slice(indices : IntRange) : List<T>
複製程式碼
接收者和返回型別都用到了函式的型別形參T
,它們的型別都是List<T>
,當在一個具體的列表上呼叫這個函式時,可以顯示地指定型別實參,也可以讓編譯器自動推匯出型別:
fun main(args: Array<String>) {
val letters = ('a'..'z').toList()
//顯示地指定型別實參。
println(letters.slice<Char>(0..2))
//編譯器自動推匯出T的型別是Char。
println(letters.slice(10..13))
}
複製程式碼
2.1.2 filter
下面再來看filter
的例子
fun main(args: Array<String>) {
val authors = listOf("first", "second")
val readers = mutableListOf("first", "third")
println(readers.filter { it !in authors })
}
複製程式碼
執行結果為:
>> [third]
複製程式碼
其中filter
的定義為:
fun <T> List<T>.filter(predicate : (T) -> Boolean) : List<T>
複製程式碼
在上面的例子中,自動生成的lambda
引數it
的型別為String
。編譯器推斷T
就是String
,因為它知道函式是在List<T>
上呼叫,而它的接收者readers
的真實型別是List<String>
。
2.1.3 宣告泛型的擴充套件屬性
我們可以給 類或介面的方法、頂層函式、擴充套件函式和擴充套件屬性 宣告型別引數,在上面的例子中,型別引數用在了接收者和lambda
引數上,下面我們再來看一個 宣告泛型的擴充套件屬性 的例子:
val <T> List<T>.penultimate: T
get() = this[size - 2]
fun main(args: Array<String>) {
println(listOf(1, 2, 3, 4).penultimate)
}
複製程式碼
執行結果為:
>> 3
複製程式碼
2.1.4 不能宣告泛型的非擴充套件屬性
普通(非擴充套件)屬性 不能擁有型別引數,不能在一個型別的屬性中儲存多個不同型別的值,因此 宣告泛型非擴充套件函式沒有任何意義。
2.2 泛型類和泛型介面
Kotlin
通過在類名稱後加上一對尖括號,並把型別引數放在尖括號內來宣告泛型類和泛型介面。一旦宣告後,就可以在型別的主體內 像其它型別一樣使用型別引數,例如List<T>
:
interface List<T> {
operator fun get(index : Int) : T
}
複製程式碼
如果你的類繼承了泛型類,或者實現了泛型介面,就得 為基礎型別的泛型形參提供一個型別實參,它可以是一個 具體型別或者是另一個型別形參。
2.2.1 將型別形參替換為具體型別
下面我們先定義一個泛型類Holder<T>
,再將它的型別形參替換為具體型別Int
:
interface Holder<T> {
fun getValue() : T
fun setValue(t : T)
}
class HolderInt : Holder<Int> {
var a : Int = 0;
override fun getValue() = a
override fun setValue(value : Int) {
a = value
}
}
fun main(args: Array<String>) {
val t = HolderInt()
t.setValue(2)
println("value=${t.getValue()}")
}
複製程式碼
執行結果為:
>> value=2
複製程式碼
2.2.2 將型別形參替換為另一個型別形參
HolderWrapper
定義了它自己的型別引數T
並把它指定為父類的型別引數,它是全新的型別形參,不必保留一樣的名稱:
interface HolderWrapper<T> : Holder<T>
class HolderInt : HolderWrapper<Int> {
var a : Int = 0;
override fun getValue() = a
override fun setValue(value : Int) {
a = value
}
}
複製程式碼
2.2.3 類自身作為型別實參引用
一個類甚至可以把它自己作為型別實參引用,實現Comparable
介面的類就是這種模式的經典例子,任何可以比較的元素都必須定義它如何與同樣型別的物件比較。
interface Comparable<T> {
fun compareTo(other : T) : Int
}
class String : Comparable<String> {
override fun compareTo(other : String) : Int = /** **/
}
複製程式碼
String
類實現了Comparable
泛型介面,提供型別String
給型別實參T
。
2.3 型別引數約束
型別引數約束 可以限制作為 泛型類和泛型函式的型別實參的型別。例如計算列表元素之和的函式sum
,它可以用在List<String>
和List<Double>
上,但不可用在List<String>
上,可以 定義一個型別引數約束,說明sum
的型別形參必須是數字。
如果你把一個型別指定為泛型型別形參的上界約束,在泛型型別具體的初始化中,其對應的型別實參就必須是這個具體型別或者它的子型別。約束的定義方式為:把冒號放在型別引數名稱之後,作為型別形參上界的型別緊隨其後,例如:
fun <T : Number> List<T>.sum() : T
複製程式碼
如果需要在一個型別引數上指定多個約束,需要使用不同的語法:
fun <T> ensureTrailingPeriod(seq : T)
where T : CharSequence, T : Appendable {
//...
}
複製程式碼
在這種情況下,作為型別實參的型別必須實現CharSequence
和Appendable
兩個介面。
2.4 讓型別實參非空
如果你宣告的是泛型類或者泛型函式,包括可空的型別實參在內,任何型別實參都可以替換它的型別形參。事實上,沒有指定上界的型別形參將會使用Any?
作為預設的上界,關於Any
的含義可以參考 Kotlin 知識梳理(7) - Kotlin 的型別系統 中的2.4
節。
如果你想保證替換型別形參的始終是非空型別,可以通過指定一個約束來實現,如果除了可空性之外沒有任何限制,那麼可以使用Any
代替預設的Any?
作為上界。
class Processor<T : Any> {
fun process(value : T) {
value.hashCode()
}
}
複製程式碼
除了Any
之外,還可以指定任意非空型別作為上界,來讓型別引數非空。
更多文章,歡迎訪問我的 Android 知識梳理系列:
- Android 知識梳理目錄:www.jianshu.com/p/fd82d1899…
- 個人主頁:lizejun.cn
- 個人知識總結目錄:lizejun.cn/categories/