關于什么是泛型, 看另一篇文章 http://www.reibang.com/p/7eac2f36036e
參數(shù)化函數(shù)(泛型函數(shù))
- 在函數(shù)名前邊加上
<T>
實現(xiàn)參數(shù)化函數(shù) - 函數(shù)的參數(shù)至少有一個是T類型, 否則編譯器無法推斷出T的類型
fun <T> foo (input: T): T {
return input
}
函數(shù)的返回值可以是任何類型, 但不能用運行時的類型
fun <T> foo (input: T): String {
return input // 即使我們在運行時傳入了一個字符串也不行, input的類型是T, 與運行時的類型無關
}
參數(shù)化類型
參數(shù)化類型主要用在各種容器類型上, 這種類型內(nèi)部可以包含其他類型的數(shù)據(jù), 比如list, map
class Sequence<T> // 定義一個參數(shù)化類型
val seq = Sequence<Int>() //傳入Int類型
上界類型約束
將類型限制為某個類的子類, 如果省略, 將會是Any
fun <T: Number> convert(a: T, b: T) // 將T的類型限制為Number的子類
fun <T> convert(a: T, b: T) // T的類型限制為Any
Invariance (不變)
類型不變指泛型類型默認是沒有繼承上的關系的, M<Int>
并不是M<Number>
的一個子類型.
這樣設計的原因, 參考這個例子:
fun foo(m: M<Number>): Unit {
m.add(123L)
}
假設有一個M<Int>型的集合x
, 執(zhí)行foo(x)
后, 如果類型有繼承關系, 由于Int
和Long
都是Number
的子類型, 就會往x
里添加一個Long
型元素, 這違背了類型安全的原則.
Covariance (協(xié)變)
協(xié)變是改變類型之間的關系, 使他們有繼承性.
fun foo(m: M<Number>): Unit {
m.functionFromNumber()
}
假設foo
函數(shù)會調(diào)用一個Number
類的方法, 這樣我們就不用管傳入的是m<int>
, 還是m<Long>都無所謂(因為函數(shù)定義在父類上), 此時就需要
協(xié)變`來讓類型有繼承關系.
class M<out T>
使用out
將T
定義為協(xié)變類型之后, 不能用T
作為函數(shù)的輸入?yún)?shù)(形參), 可以做返回值. 像m.add(T)
是非法的.
逆變 Contravariance
逆變是反轉兩個類的繼承關系, 比如逆變后, M<Int> 是 M<Number> 的父類.
這個需求是這樣的
Event<String>(stringHandler)
Event<Number>(numberHandler)
我們有兩種Event
, 分別用對應類型的Handler處理, 假設我們想用一個通用的handler比如: commonHandler<Any>`來處理.
Event<String>(commonHandler)
Event<Number>(commonHandler)
答案是不行, 因為Any
是String
的父類, 而Event
只能使用String
或它的子類. 通過逆變, Any
就變成了String
的子類.
class Event<in T>(val handler: Handler<T>)
class Handler<in T>
...
逆變后, 類型只能作為輸入?yún)?shù)(形參), 不能作為返回值類型.
type projection
類型的variance有兩種, use site(java) 和 declaration site(Kotlin), type projection 允許我們使用use site variance, 它的思想是, 如果我們無法在類定義(比如使用別人的類)時使用covariance或contravriance(即declaration site), 我們可以在定義函數(shù)時, 規(guī)定函數(shù)將如何使用類型.
fun foo(m: M<out Number>): Unit {
m.functionFromNumber()
}
同理, contravariant
class Event<in T>(val handler: Handler<in T>) // 構造函數(shù)的in T
type projection定義了我們?nèi)绾问褂煤瘮?shù), 而不需修改原類型聲明
Type reification
由于JVM在編譯時會把所有泛型類型(不包括基礎數(shù)據(jù)類型)信息擦除, kotlin同樣如此, 使用Type reification, kotlin可以為inline 函數(shù)保留泛型類型信息.
inline fun <reified T>printT(any: Any): Unit {
if (any is T)
println("I am a tee: $any")
}
這個函數(shù)可以在運行時獲取到T
的傳入類型. 類型具體化只能用于inline
函數(shù), 因為inline
函數(shù)的執(zhí)行是把函數(shù)體內(nèi)容直接拷貝到調(diào)用處, 此時通過傳遞的參數(shù)可以知道T的類型. 其他函數(shù)無法使用類型具體化