泛型,即 "參數(shù)化類型",將類型參數(shù)化,可以用在類、接口、方法上。
與Java語言中的非常相似,但是Kotlin語言的創(chuàng)建者試圖通過引入特殊的關(guān)鍵字(如out和in)來使它們更加直觀和易于理解。
以下是使用泛型的主要優(yōu)點:
- 類型安全:通用允許僅保留單一類型的對象。泛型不允許存儲其他對象。
- 不需要類型轉(zhuǎn)換:不需要對對象進(jìn)行類型轉(zhuǎn)換。
- 編譯時間檢查:在編譯時檢查泛型代碼,以便在運行時避免任何問題
泛型類
像 java 一樣,Kotlin 中的類可以擁有類型參數(shù):
class Box<T>(t: T){
var value = t
}
通常來說,創(chuàng)建一個這樣類的實例,我們需要提供類型參數(shù):
val box: Box<Int> = Box<Int>(1)
但如果類型有可能是推斷的,比如來自構(gòu)造函數(shù)的參數(shù)或者通過其它的一些方式,一個可以忽略類型的參數(shù):
val box = Box(1) //1是 Int 型,因此編譯器會推導(dǎo)出我們調(diào)用的是 Box<Int>
泛型接口
聲明泛型接口的格式與聲明泛型類相似,定義如下泛型接口。
interface GenericsInterface<T> {
public T generate();
}
在實現(xiàn)泛型接口的類中,指定泛型實參。
class GenericsClass implements GenericsInterface<String> {
public String generate() {
return "hello";
}
}
泛型函數(shù)
范型函數(shù)
函數(shù)也可以像類一樣有類型參數(shù)。類型參數(shù)在函數(shù)名之前:
fun <T> singletonList(item: T): List<T> {
// ...
}
fun <T> T.basicToString() : String { // extension function
// ...
}
調(diào)用范型函數(shù)需要在函數(shù)名后面制定類型參數(shù):
val l = singletonList<Int>(1)
范型約束
指定類型參數(shù)代替的類型集合可以用通過范型約束進(jìn)行限制。
上界(upper bound):最常用的類型約束是上界,在 Java 中對應(yīng) extends關(guān)鍵字:
fun <T : Comparable<T>> sort(list: List<T>) {
// ...
}
冒號后面指定的類型就是上界:只有 Comparable<T>的子類型才可以取代 T 比如:
sort(listOf(1, 2, 3)) // OK. Int is a subtype of Comparable<Int>
sort(listOf(HashMap<Int, String>())) // Error: HashMap<Int, String> is not a subtype of Comparable<HashMap<Int, String>>
默認(rèn)的上界是 Any?。在尖括號內(nèi)只能指定一個上界。如果要指定多種上界,需要用 where 語句指定:
fun <T> cloneWhenGreater(list: List<T>, threshold: T): List<T>
where T : Comparable,
T : Cloneable {
return list.filter { it > threshold }.map { it.clone() }
}
聲明處變型*
java聲明處變型
假如有個范型接口Source<T>,沒有任何接收 T 作為參數(shù)的方法,唯一的方法就是返回 T:
// Java
interface Source<T> {
T nextT();
}
存儲一個Source<String>的實例引用給一個類型為 Source<Object> 是十分安全的。但 Java并不知道,而且依然禁止這么做:
// Java
void demo(Source<String> strs) {
Source<Object> objects = strs; // !!! Not allowed in Java
// ...
}
為此,我們不得不聲明對象類型為 Source<? extends Object>,這樣做并沒有太大的意義,因為我們可以像以前一樣調(diào)用所有方法,因此并沒有通過復(fù)雜的類型添加什么值。但編譯器不知道。
Kotlin聲明處變型
在 Kotlin 中,有種可以將這些東西解釋給編譯器的辦法,叫做聲明處變型:通過注解類型參數(shù) T 的來源,來確保它僅從 Source<T> 成員中返回(生產(chǎn)),并從不被消費。 為此,我們提供 out 修飾符:
abstract class Source<out T> {
abstract fun nextT(): T
}
fun demo(strs: Source<String>) {
val objects: Source<Any> = strs // This is OK, since T is an out-parameter
// ...
}
一般原則是:當(dāng)一個類 C 的類型參數(shù) T 被聲明為 out 時,它就只能出現(xiàn)在 C 的成員的輸出-位置,結(jié)果是 C<Base> 可以安全地作為 C<Derived>的超類。
更聰明的說法就是,當(dāng)類 C 在類型參數(shù) T 之下是協(xié)變的,或者 T 是一個協(xié)變類型??梢园?C 想象成 T 的生產(chǎn)者,而不是 T 的消費者。
out 修飾符本來被稱之為變型注解,但由于同處與類型參數(shù)聲明處,我們稱之為聲明處變型。這與 Java 中的使用處變型相反。
另外除了 out,Kotlin 又補充了一個變型注釋:in。
它接受一個類型參數(shù)逆變:只可以被消費而不可以 被生產(chǎn)。非變型類的一個很好的例子是 Comparable:
abstract class Comparable<in T> {
abstract fun compareTo(other: T): Int
}
fun demo(x: Comparable<Number>) {
x.compareTo(1.0) // 1.0 has type Double, which is a subtype of Number
// Thus, we can assign x to a variable of type Comparable<Double>
val y: Comparable<Double> = x // OK!
}
使用處變型:類型投影*
聲明類型參數(shù) T 為 out 很方便,而且可以避免在使用出子類型的麻煩,但有些類 不能 限制它只返回 T ,Array 就是一個例子:
class Array<T>(val size: Int) {
fun get(index: Int): T { /* ... */ }
fun set(index: Int, value: T) { /* ... */ }
}
這個類既不能是協(xié)變的也不能是逆變的,這會在一定程度上降低靈活性。考慮下面的函數(shù):
fun copy(from: Array<Any>, to: Array<Any>) {
assert(from.size == to.size)
for (i in from.indices)
to[i] = from[i]
}
該函數(shù)作用是復(fù)制 array ,讓我們來實際應(yīng)用一下:
val ints: Array<Int> = arrayOf(1, 2, 3)
val any = Array<Any>(3) { "" }
copy(ints, any) // Error: expects (Array<Any>, Array<Any>)
這里我們又遇到了同樣的問題 Array<T> 中的T 是不可變型的,因此 Array<Int> 和 Array<Any> 互不為對方的子類,導(dǎo)致復(fù)制失敗。為什么呢?應(yīng)為復(fù)制可能會有不合適的操作,比如嘗試寫入,當(dāng)我們嘗試將 Int 寫入 String 類型的 array 時候?qū)?dǎo)致 ClassCastException 異常。
我們想做的就是確保 copy() 不會做類似的不合適的操作,為阻止向from寫入,我們可以這樣:
fun copy(from: Array<out Any>, to: Array<Any>) {
// ...
}
這就是類型投影:這里的from不是一個簡單的 array, 而是一個投影,我們只能調(diào)用那些返回類型參數(shù) T 的方法,在這里意味著我們只能調(diào)用get()。這是我們處理調(diào)用處變型的方法,類似 Java 中Array<? extends Object>,但更簡單。
當(dāng)然也可以用in做投影:
fun fill(dest: Array<in String>, value: String) {
// ...
}
Array<in String> 對應(yīng) Java 中的 Array<? super String>,fill()函數(shù)可以接受任何CharSequence 類型或 Object類型的 array 。
參考文章:
kotlin-in-chinese