Kotlin 泛型
泛型,即 "引數化型別",將型別引數化,可以用在類,介面,方法上。
與 Java 一樣,Kotlin 也提供泛型,為型別安全提供保證,消除型別強轉的煩惱。
宣告一個泛型類:
class Box<T>(t: T) { var value = https://www.itread01.com/kotlin/t }
建立類的例項時我們需要指定型別引數:
val box: Box<Int> = Box<Int>(1) // 或者 val box = Box(1) // 編譯器會進行型別推斷,1 型別 Int,所以編譯器知道我們說的是 Box<Int>。
以下例項向泛型類 Box 傳入整型資料和字串:
class Box<T>(t : T) { var value = https://www.itread01.com/kotlin/t } fun main(args: Array) { var boxInt = Box (10) var boxString = Box ("itread01") println(boxInt.value) println(boxString.value) }
輸出結果為:
10 itread01
定義泛型型別變數,可以完整地寫明型別引數,如果編譯器可以自動推定型別引數,也可以省略型別引數。
Kotlin 泛型函式的宣告與 Java 相同,型別引數要放在函式名的前面:
fun <T> boxIn(value: T) = Box(value) // 以下都是合法語句 val box4 = boxIn<Int>(1) val box5 = boxIn(1) // 編譯器會進行型別推斷
在呼叫泛型函式時,如果可以推斷出型別引數,可以省略泛型引數。
以下例項建立了泛型函式 doPrintln,函式根據傳入的不同型別做相應處理:
fun main(args: Array<String>) { val age = 23 val name = "itread01" val bool = true doPrintln(age) // 整型 doPrintln(name) // 字串 doPrintln(bool) // 布林型 } fun <T> doPrintln(content: T) { when (content) { is Int -> println("整型數字為 $content") is String -> println("字串轉換為大寫:${content.toUpperCase()}") else -> println("T 不是整型,也不是字串") } }
輸出結果為:
整型數字為 23 字串轉換為大寫:itread01 T 不是整型,也不是字串
泛型約束
我們可以使用泛型約束來設定一個給定引數允許使用的型別。
Kotlin 中使用 : 對泛型的型別上限進行約束。
最常見的約束是上界(upper bound):
fun <T : Comparable<T>> sort(list: List<T>) { // …… }
Comparable
sort(listOf(1, 2, 3)) // OK。Int 是 Comparable<Int> 的子型別 sort(listOf(HashMap<Int, String>())) // 錯誤:HashMap<Int, String> 不是 Comparable<HashMap<Int, String>> 的子型別
預設的上界是 Any?。
對於多個上界約束條件,可以用 where 子句:
fun <T> copyWhenGreater(list: List<T>, threshold: T): List<String> where T : CharSequence, T : Comparable<T> { return list.filter { it > threshold }.map { it.toString() } }
型變
Kotlin 中沒有萬用字元型別,它有兩個其他的東西:宣告處型變(declaration-site variance)與型別投影(type projections)。
宣告處型變
宣告處的型別變異使用協變註解修飾符:in、out,消費者 in, 生產者 out。
使用 out 使得一個型別引數協變,協變型別引數只能用作輸出,可以作為返回值型別但是無法作為入參的型別:
// 定義一個支援協變的類 class itread01<out A>(val a: A) { fun foo(): A { return a } } fun main(args: Array<String>) { var strCo: itread01<String> = itread01("a") var anyCo: itread01<Any> = itread01<Any>("b") anyCo = strCo println(anyCo.foo()) // 輸出 a }
in 使得一個型別引數逆變,逆變型別引數只能用作輸入,可以作為入參的型別但是無法作為返回值的型別:
// 定義一個支援逆變的類 class itread01<in A>(a: A) { fun foo(a: A) { } } fun main(args: Array<String>) { var strDCo = itread01("a") var anyDCo = itread01<Any>("b") strDCo = anyDCo }
星號投射
有些時候, 你可能想表示你並不知道型別引數的任何資訊, 但是仍然希望能夠安全地使用它. 這裡所謂"安全地使用"是指, 對泛型型別定義一個型別投射, 要求這個泛型型別的所有的實體例項, 都是這個投射的子型別。
對於這個問題, Kotlin 提供了一種語法, 稱為 星號投射(star-projection):
- 假如型別定義為 Foo<out T> , 其中 T 是一個協變的型別引數, 上界(upper bound)為 TUpper ,Foo<> 等價於 Foo<out TUpper> . 它表示, 當 T 未知時, 你可以安全地從 Foo<> 中 讀取TUpper 型別的值.
- 假如型別定義為 Foo<in T> , 其中 T 是一個反向協變的型別引數, Foo<> 等價於 Foo<inNothing> . 它表示, 當 T 未知時, 你不能安全地向 Foo<> 寫入 任何東西.
- 假如型別定義為 Foo<T> , 其中 T 是一個協變的型別引數, 上界(upper bound)為 TUpper , 對於讀取值的場合, Foo<*> 等價於 Foo<out TUpper> , 對於寫入值的場合, 等價於 Foo<in Nothing> .
如果一個泛型型別中存在多個型別引數, 那麼每個型別引數都可以單獨的投射. 比如, 如果型別定義為interface Function<in T, out U> , 那麼可以出現以下幾種星號投射:
- Function<*, String> , 代表 Function<in Nothing, String> ;
- Function<Int, *> , 代表 Function<Int, out Any?> ;
- Function<, > , 代表 Function<in Nothing, out Any?> .
注意: 星號投射與 Java 的原生型別(raw type)非常類似, 但可以安全使用