參考:
- Kotlin 實戰(zhàn)
- Java 泛型推薦閱讀:https://www.zhihu.com/question/20400700
代碼與說明
Kotlin 分享系列垂睬,來自部門團(tuán)隊幾個伙伴一起整理蒋困,全部MD與代碼都在:
https://github.com/zhaoyubetter/KotlinShare
Kotlin 泛型基本上與Java泛型類型差购,了解了Java泛型焕参,基本就了解了Kotlin的泛型
Kotlin 泛型內(nèi)容包括:
- 泛型函數(shù)與類
- 類型擦除與實化類型參數(shù)
- 聲明點(diǎn)變形與使用點(diǎn)型變
實化類型參數(shù)
允許再運(yùn)行時的內(nèi)聯(lián)函數(shù)
調(diào)用中引用作為類型實參的具體類型拭嫁;
聲明點(diǎn)變型
說明一個帶類型參數(shù)的泛型類型氨肌,是否是另一個泛型類型的子或超類型
捂寿;
使用點(diǎn)變型
在具體使用一個泛型類型時,達(dá)到與Java通配符一樣的效果魁瞪;
1. 泛型類型參數(shù)
可以定義帶類型形參
的類型,當(dāng)此類型的實例被創(chuàng)建時穆律,類型形參被替換成類型實參
的具體類型惠呼,如:List<String>, Map<K, V> 等;
在使用時峦耘,Kotlin 編譯器能推導(dǎo)出類型實參
:
val list = listOf("ABC", “DEF”) // 等價于 val list = listOf<String>("ABC", “DEF”)
1.1 泛型函數(shù)和屬性
這個概念跟Java一樣剔蹋,泛型函數(shù)有自己的類型形參
,泛型函數(shù)使用時辅髓,在調(diào)用初泣崩,會被替換成具體的類型實參
;
比如:
fun <T> List<T>.slice(incides:IntRange):List<T>
泛型形參聲明
<T> 放在 fun
關(guān)鍵字之后利朵,使用跟Java類似律想;
泛型的擴(kuò)展屬性
val <T> List<T>.penultimate: T
get() = this[size - 1]
注意: 不能聲明泛型非擴(kuò)展屬性,不能在一個類的屬性中存儲多個不同類型的值绍弟;如要這么做技即,需考慮泛型類
1.3 泛型類、接口
與Java一樣樟遣,在類聲明時而叼,可指定泛型,一旦聲明之后豹悬,就可以在類的主體中像其他類型一樣使用類型參數(shù)
了葵陵;如:
interface List<T> {
operator fun get(index: Int) : T // ....
}
如果類繼承自泛型類(接口),就得為基礎(chǔ)類型的泛型形參提供具體類型實參
或者另外的類型形參
比如:
interface List<T>
class StringList : List<String> // 具體類型實參
class MyList<T> : List<T> // 泛型類型形參
1.4 類型參數(shù)約束
約束用來說明瞻佛,只能使用什么樣的類型實參
;
上界約束:
在泛型類型具體的初始化中脱篙,其對應(yīng)的類型實參,必須是 具體類型伤柄,或者子類型;
如下:(Java 使用 <T extends Number>)
fun <T: Number> List<T>.sum():T // : Number 表示上界
// java
public <T extends Number> void test(T t) {
}
注意:這里不涉及到 out绊困、in 生產(chǎn)者,消費(fèi)者關(guān)系适刀,在參數(shù)位置秤朗,才涉及到,他們在后面笔喉;
我們也可以指定多個約束取视,如同Java 中 (<T extends Number & Appendable>),但Kotlin語法有點(diǎn)奇怪常挚,使用where
fun <T> List<T>.sum2():T where T : Number , T: Appendable {}
1.5 讓類型形參非空
沒有指定上界的類型形參將會使用 Any?
這個默認(rèn)上界作谭;
如下:
class Processor<T> {
fun process(value: T) {
value?.hashCode()
}
}
fun main(args: Array<String>) {
// 可空類型String?被用來替換T
val nullableString = Processor<String?>()
// 可傳遞null
nullableString.process(null)
}
如何不允許null呢,使用<T:Any>
來確保類型T是非空類型奄毡;
class Processor<T : Any> {
fun process(value: T) {
value.hashCode() // ?. 可以去掉了
}
}
2. 運(yùn)行時的泛型:擦除和實化類型參數(shù)
Java中折欠,泛型通過類型擦除實現(xiàn);
在Kotlin可通過聲明一個inline
函數(shù)實現(xiàn)類型實參,不被擦除(Kotlin稱實化)怨酝;
2.1 運(yùn)行時的泛型:類型檢查和轉(zhuǎn)換
跟Java類似,Kotlin中的泛型在運(yùn)行時也被擦除了那先;擦除是有好處的农猬,這樣保存在內(nèi)存中類型信息就少了;
在Kotlin中售淡,不允許使用沒有指定類型實參
的泛型類型斤葱,如果想判斷一個變量是否是列表,可傳遞 *
星投影揖闸;
val list = listOf(1,2,3)
if(list is List<*>) { // 星投影揍堕,類似Java的 <?>
}
使用 as 、 as?
進(jìn)行轉(zhuǎn)換:
fun printTest(c: Collection<*>) {
val intList = c as? kotlin.collections.List<Int> ?:
throw IllegalArgumentException("轉(zhuǎn)換失敗")
println(intList)
}
2.2 聲明帶實化類型參數(shù)的函數(shù)
因為泛型會被擦除汤纸,比如下面的代碼是報錯的:
fun <T> isA(value: Any) = value is T // 不能確定T
但通過inline
內(nèi)聯(lián)函數(shù)衩茸,會把每一個的函數(shù)調(diào)用換成實際的代碼調(diào)用,lambda 也是一樣贮泞,并結(jié)合 reified
標(biāo)記類型參數(shù)
楞慈,上面的 value is T
就可以通過編譯了
inline fun <reified T> isA(value: Any) = value is T
fun main(args: Array<String>) {
println(isA<Int>(1))
}
為什么實化只對內(nèi)聯(lián)函數(shù)有效
在內(nèi)聯(lián)函數(shù)中 可以寫
value is T
,普通類啃擦、普通函數(shù)中卻不可以囊蓝;
因為編譯器將內(nèi)聯(lián)函數(shù)的字節(jié)碼直接添加調(diào)用處,當(dāng)每次調(diào)用帶實化類型參數(shù)的函數(shù)時令蛉,編譯器知道了類型實參的確切類型聚霜;而kotlin 中調(diào)用,不能省略類型實參, 上面的 不能寫成 isA(1)珠叔,編譯直接報錯
注意 帶reified
的內(nèi)聯(lián)函數(shù)不能再Java中調(diào)用蝎宇,普通的 inline 函數(shù)可以;
3. 變型:泛型和子類型化
變型
描述了擁有相同基礎(chǔ)類型
和不同類型
實參(泛型)類型之間是如何關(guān)聯(lián)的:如运杭,List<String> 與 List<Any>;
變型
的意義在于設(shè)計的API夫啊,不會以不方便的方式限制用戶,也不會破壞用戶所期望的類型安全辆憔;
3.1 為什么存在變型: 給函數(shù)傳遞實參
把一個 List<String
類型的變量傳給 List<Any>
這樣是允許的撇眯,如:
fun printContents(c: List<Any>) {
println(c.joinToString(""))
}
fun main(args: Array<String>) {
printContents(listOf("a","b"))
}
但是下面的代碼
fun addContent(list: MutableList<Any>) {
list.add(1234)
}
// 下面的代碼調(diào)用,明顯有問題
val list = mutableListOf<String>("cccc")
addContent(list) // 編譯通不過
3.2 類虱咧、類型與子類型
變量的類型熊榛,規(guī)定了該變量的可能值,類型和類
不是相同的概念腕巡;
非泛型類 類的名稱
可以直接當(dāng)做類型
使用玄坦;如:
var s : String
var s : String?
每個Kotlin的
類
都可以用于構(gòu)造至少2種類型
;
泛型類 情況復(fù)雜,要得到一個合法的類型,需要用類型實參
替換(泛型)類的類型形參
煎楣;
如:List
不是一個類型(它是一個類)豺总,List<Int>、List<String?>
是合法的類型择懂;
子類型喻喳、超類型
用來描述類型
之間的關(guān)系,
如果需要類型A
的值,都能夠使用類型B
的值(當(dāng)做A的值)困曙,則類型B就稱為類型A的子類型表伦;超類型
反之;
比如:Number 是 Int 的超類型慷丽,Int 是Number 的子類型蹦哼;
這樣的情況下,
子類型
與子類
本質(zhì)上是同一回事;
當(dāng)涉及到泛型類型時要糊,子類型與子類就有差異了纲熏;List<String> 是 List<Any> 的子類型嗎?對于只讀List接口杨耙,是的赤套,而:MutableList<String> 當(dāng)做Mutable<Any>的子類型是不安全的;
一個泛型類 - 如:MutableList 如果任意2種類型A和B珊膜,MutableList<A> 既不是MutableList<B>的子類型容握,也不是它的超類型,稱為在該類型參數(shù)
上是不變型
的车柠;
對于List,Kotlin 中的List接口表示的是只讀集合剔氏,如果A是B的子類型,那List<A> 是 List<B> 的子類型竹祷,這樣的類or接口被稱為協(xié)變
谈跛;
3.3 協(xié)變:保留子類型化關(guān)系
協(xié)變說明子類型化
被保留了, 在Kotlin中,要聲明類在某個類型參數(shù)上是協(xié)變的塑陵,在該類型參數(shù)的名稱上添加 out
關(guān)鍵字感憾;
interface Producter<out T> {
fun produce() : T
}
有什么用?看例子
open class Animal {
fun feed() {
}
}
// 泛型類令花,接收Animal子類
class Herd<T : Animal> {
val size: Int get() = 20
operator fun get(i:Int) : T { ... } // 操作符重載
}
// 具體動物
class Cat : Animal() {
fun clean() {}
}
// 喂方法阻桅,不好意思,我只認(rèn) Animal兼都,不然他的子類
fun feedAll(animals : Herd<Animal>) {
for(i in 0 until animals.size) {
animals[i].feed()
}
}
fun takeCareOfCats(cats: Herd<Cat>) {
feedAll(cats) // 期望 Herd<Animal>嫂沉,很遺憾報錯了
}
怎么辦?使用out
關(guān)鍵字扮碧,改成協(xié)變
// 泛型類
class Herd<out T : Animal> {
注意:這里的<out T: Animal> 與上面的提高的
1.4 類型參數(shù)約束
是不一樣的,
類型約束,<>
在 fun 之后趟章,這里是在方法 or 類的后面杏糙;
out 位置,表示這個類只能生產(chǎn)類型T的值蚓土,而不能消費(fèi)他們宏侍;
在類成員的聲明中類型參數(shù)的使用可分為in 位置
與 out位置
interface MyTranform<T> {
fun tranform(t: T): T // 參數(shù) t,in 位置蜀漆,返回值 out位置
}
類的類型參數(shù)前的out 负芋、in關(guān)鍵字約束了使用T的可能性,保證了對應(yīng)子類型關(guān)系的安全性嗜愈;
Out 關(guān)鍵字的2個含義
- 子類型化會被保留(Producer<Cat>) 是(Producer<Animal>)的子類型;
- T 只能用在out位置(生產(chǎn)位置)
Kotlin 中的 List接口
// out 位置
public interface List<out E> : Collection<E> {...}
// T 在 in out 位置
public interface MutableList<E> : List<E>, MutableCollection<E> {...}
3.4 逆變:反轉(zhuǎn)子類型化關(guān)系
逆變
是協(xié)變的鏡像:對于逆變類莽龟,它的子類型化關(guān)系與用作類型實參
的類的子類型化關(guān)系是相反的蠕嫁;
// in 位置,表示消費(fèi)
val anyComparator = Comparator<Any> {
e1, e2 ->
e1.hashCode() - e2.hashCode()
}
fun main(args: Array<String>) {
val strings = listOf("B", "A", "War")
println(strings.sortedWith(anyComparator))
}
如需在特定類型的對象比較毯盈,可使用能處理該類型或它的超類型的比較器剃毒;
Comparator<Any> 是 Comparator<String>的子類型,其中Any是String的超類型搂赋;不同類型之間的子類型關(guān)系
與 這些類型的比較器間的子類型化關(guān)系
是相反的;
逆變
如果B是A的子類型赘阀,那么Consumer<A>
就是Consumer<B>
的子類型,類型參數(shù)A與B交換了位置脑奠;協(xié)變:子類型化關(guān)系復(fù)制了它的類型實參的子類型化關(guān)系基公,逆變則反過來
in
關(guān)鍵字:對應(yīng)類型的值是傳遞進(jìn)來給這個類的方法
,并且被方法消費(fèi)宋欺;
協(xié)變 | 逆變 | 不變 |
---|---|---|
Producer<T> | Consumer<in T> | MutableList<T> |
類的子類型化保留:Producer<Cat> 是 Producer<Animal> 的子類型 | 子類型反轉(zhuǎn):Consumer<Animal> 是 Consumer<Cat>的子類型 | 沒有子類型化 |
T只能在out位置 | T只能在in位置 | 任何位置 |
表格:協(xié)變轰豆、逆變和不變
協(xié)變 | 逆變 | 不變 |
---|---|---|
Producer<T> | Consumer<in T> | MutableList<T> |
類的子類型化保留:Producer<Cat> 是 Producer<Animal> 的子類型 | 子類型反轉(zhuǎn):Consumer<Animal> 是 Consumer<Cat>的子類型 | 沒有子類型化 |
T只能在out位置 | T只能在in位置 | 任何位置 |
類可以在一個類型參數(shù)上協(xié)變,另一個參數(shù)上逆變齿诞,比如Function
接口酸休;
public interface Function1<in P1, out R> : Function<R> {
public operator fun invoke(p1: P1): R
}
3.5 使用點(diǎn)變型:在類型出現(xiàn)的地方
聲明點(diǎn)變型
:在類聲明的時候,指定變型修飾符祷杈,這些修飾符會應(yīng)用到所有類被使用的
地方斑司;
使用點(diǎn)變型
:在Java中,使用(super, extends)通配符但汞,處理變型宿刮,使用帶類型參數(shù)時,指定是否可用這個類型參數(shù)的子或者超類替換特占;
Kotlin 聲明點(diǎn)變型 vs Java 通配符
聲明點(diǎn)變型更加簡潔糙置,指定一次變型修飾符,所有這個類的使用者是目,都會添加約束谤饭;Java 中使用:Function(? super T, ? extends R)
來創(chuàng)建約束;
如下代碼片段(發(fā)現(xiàn)了區(qū)別,聲明處變型是不是更簡潔呢揉抵?):
// Java 使用點(diǎn)變型
public interface Stream<T> {
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
}
// Kotlin 聲明處變型
public interface Function1<in P1, out R> : Function<R> {
public operator fun invoke(p1: P1): R
}
Kotlin也支持使用點(diǎn)變型
, 直接對應(yīng)Java的限界通配符亡容;
// (不變型) 從一個集合copy到另一個集合
fun <T> copyData(source: MutableList<T>, destination: MutableList<T>) {
for(item in source) {
destination += item
}
}
// 特定類型
fun <T : R, R> copyData2(source: MutableList<T>, destination: MutableList<R>) {
for (item in source) {
destination += item
}
}
// 使用點(diǎn)變型:給類型參數(shù)加上 變型修飾符 (out 投影)
fun <T> copyData3(source: MutableList<out T>, destination: MutableList<T>) {
for (item in source) {
destination += item
}
}
// 測試函數(shù)
fun main(args: Array<String>) {
// copyData 不變型
val source = mutableListOf("abc", "efg")
val destination = mutableListOf<String>()
copyData(source, destination)
// copyData2
val source2 = mutableListOf("abc", "efg")
var destination2 = mutableListOf<Any>()
copyData2(source2, destination2)
// copyData3 使用點(diǎn)變型
val source3 = mutableListOf("better", "cc")
var destination3 = mutableListOf<Any>()
copyData3(source3, destination3)
println(destination3)
}
類型投影
可以為類型聲明中類型參數(shù)指定變型修飾符,包括:形參類型(方法上的)冤今,局部變量類型闺兢、函數(shù)返回類型等,這稱作類型投影
戏罢;投影即受限屋谭;
如上copyData3
函數(shù)的 source
不是一個常規(guī)的的MutableList,而是一個投影(受限)
的MutableList龟糕。只能調(diào)用返回類型是泛型類型參數(shù)的那些方法桐磁,也就是out位置的方法;
3.6 * 星號投影
星號投影讲岁,用來表示不知道關(guān)于泛型實參的任何信息我擂,跟Java的?
問號通配符類似;
注意
MutableList<*> 和 MutableList<Any?> 不一樣缓艳;
MutableList<T> 在T上是不變型的
校摩,
- MutableList<Any?>包含的是任何類型的元素;
- MutableList<*>是包含某種
特定類型
元素阶淘,但不知是哪個類型衙吩,所以不能寫入;但可讀认稀分井;
val list: MutableList<Any?> = mutableListOf('x', 1, "efg")
list.add(5)
val chars = mutableListOf('a', 'b', 'c')
val unkownElems: MutableList<*> = if (Random().nextBoolean()) list else chars
// unkownElems.add(12) // 不能調(diào)用
println(unkownElems.get(0))
上例中,編譯器將MutableList<*> 當(dāng)做out投影的類型
MutableList<out Any?>
霉猛,不能讓她消費(fèi)任何東西尺锚;
用處:
當(dāng)類型實參的信息并不重要時,可使用星號投影的語法:
- 不需要使用任何在簽名中引用類型參數(shù)的方法惜浅;
- 只是讀取數(shù)據(jù)而不關(guān)系它的具體類型瘫辩;
fun <T> getFirst(list: List<*>): T? { // 星號投影
if (!list.isEmpty()) {
return list.first() as T
}
return null
}
fun <T> getFirst2(list: List<T>): T {
return list.first()
}