前言
在Kotlin中,總的來說,可以理解泛型爲:
①向上解決類型不通用
②向下解決類型限定
如果看着累,建議先看小結,寥寥幾字,大致瞭解下。
需要理解幾個問題?
① 理解 型變是什麼?逆變又是什麼?
② 泛型存在的價值是什麼?
③ 泛型註解out、in有什麼用?Invariant又是什麼?
④ 類型擦除 有什麼影響?
本節的目錄結構是這樣的
- 泛型說明
- 型變
- 類型投影
- 泛型函數
- 泛型約束
- 類型擦除
泛型說明
Kotlin的泛型,功能與 Java 一樣
看一個範例
fun main(args: Array<String>) {
val age = 23
val name = "runoob"
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
字符串轉換爲大寫:RUNOOB
T 不是整型,也不是字符串
這便是泛型了,範例中允許接受不同的類型,類型通用。
型變
即類型轉變。通過轉變類型,提升API的適配度。
java爲什麼需要通配符來提升API的靈活性?
我們來看看Java的型變是怎麼樣的。
- Java的泛型是不型變的
- 要想實現多類型的列表存儲,就需要有很多個不同的定義,這就很麻煩了
- 必須要有一個 xxx 提供一個通用的解決方案,
否則,列表跟數組又有何區別~
聲明處型變(declaration-site variance)
即在聲明變量、參數時就確定T的類型。
說起來有點繞口,來看範例,進行一下對比
// Java
void demo(Source<String> strs) {
Source<Object> objects = strs; // !!!在 Java 中不允許
// ……
}
// Kotlin
interface Source<out T> {
fun nextT(): T
}
fun demo(strs: Source<String>) {
val objects: Source<Any> = strs // 這個沒問題,因爲 T 是一個 out-參數
// ……
}
注意看這裏Source部分
- java是不支持直接轉變的
- Kotlin支持
在Source是使用處,Kotlin的型變解決了 子類型化的麻煩。
這裏引入一個概念,型變註解:這裏的out
同樣的型變註解還有 in
類型投影(type projections)
投影就是 某事物的影子,影子之下才有天地。
只有在影子內的東西方可以動。
說的就是 限定了。
使用處型變:類型投影
這講的是一種將 某個類型投影 到某處。
你想想一下,只允許投影之下的暗處,這不正是限制嗎?
類型投影,不就是類型限制嗎?怎麼限定呢?通過out\in的類型註解。
來看個範例
fun copy(from: Array<Any>, to: Array<Any>) {
assert(from.size == to.size)
for (i in from.indices)
to[i] = from[i]
}
fun main() {
val ints: Array<Int> = arrayOf(1, 2, 3)
val any = Array<Any>(3) { "" }
copy(ints, any) // 這裏編譯是不通過的,ints類型爲 Array<Int> 但此處期望 Array<Any>
}
會碰上什麼問題呢?
Array 在 T 上是不型變的,因此 Array 和 Array 都不是另一個的子類型。
爲什麼? 再次重複,因爲 copy 可能做壞事。爲了避免這種錯誤,編譯器禁止了這種操作。
我們唯一要確保的是 copy() 不會做任何壞事。我們想阻止它寫到 from去。
fun copy(from: Array, to: Array) { …… }
這便是類型投影。所有out(協變)是輸出,in(逆變)是輸入。
複習一下out、in
對於 out 泛型,可以將使用子類泛型的對象 -> 賦值給使用父類泛型的對象。
對於 in 泛型,可以將使用父類泛型的對象 -> 賦值給使用子類泛型的對象。
星投影
這使用於並不知道類型參數的任何信息。 * 代表通喫。
這個泛型類型的所有的實體實例, 都是這個投射的子類型。
看個例子
Function<*, String>
這裏用到了*
var list:ArrayList<> = arrayListOf(1) //<>必不可少 相當於java的無泛型
當 cList:ArrayList<> = javaList 時<>相當於
當 javaList:ArrayList<> = cList 時<>相當於
這就是什麼都接收了
泛型函數
在簡單的學習下,泛型的函數是怎麼編寫的
fun <T> singletonList(item: T): List<T> {
// ……
}
fun <T> T.basicToString(): String { // 擴展函數
// ……
}
這麼用
val l = singletonList(1)
可以省略能夠從上下文中推斷出來的類型參數
val l = singletonList(1)
泛型約束
這跟java是類似的
//泛型約束 <佔位符:類型>
fun <T:Number> play(vararg param: T):Double{
return param.sumByDouble { it.toDouble() }
}
//多個約束,T有多個上限 , where T:類型,T:類型
fun <T> getBetterBig(list:Array<T>,threhold:T):List<T> where T:Number,T:Comparable<T>{
return list.filter { it>= threhold }.sorted()
}
類型擦除
泛型聲明的類型安全檢測是僅在編譯期進行的。
例如,Foo 與 Foo<Baz?> 的實例都會被擦除爲 Foo<*>。
運行時泛型類型的實例不保留關於其類型實參的任何信息。
運行時不保留其類型實參的任何信息,即類型擦除。
編譯器會禁止由於類型擦除而無法執行的 is 檢測
看個熟悉的範例
fun handleStrings(list: List<String>) {
if (list is ArrayList) {
// `list` 會智能轉換爲 `ArrayList<String>`
}
}
泛型函數調用的類型參數也同樣只在編譯期檢
這便是
在編譯器會進行安全類型檢查,在運行期則擦除類型,相當於是*類型
小結
泛型,牽扯到 Out (協變)、In(逆變)、Invariant(不變),這是必須要理解的部分。
所謂泛型,即 類型限制,類型轉變的稱呼。