Kt中的泛型是一大特色盈蛮!和Java不太相似,或者說是補(bǔ)齊了Java的坑技矮!
簡單的使用泛型抖誉,不再贅述!
在Java中衰倦,使用泛型的時候袒炉,可以使用 extend
關(guān)鍵字表示當(dāng)前的泛型必須是某個類的子類!才能正常使用樊零! Kt中當(dāng)然也支持這樣的方法我磁! kt中叫上界孽文!
// 這里我們指定了,T的上界是Number ! T 實(shí)際的類型夺艰,必須是 Number 或者 Number的子類芋哭!
fun <T : Number> List<T>.sum() : T
上界還有一個作用!就是指定泛型為不可空類型郁副!如果你的上界不是一個可空類型减牺,那么你的泛型就不能被替換成可空的類型!
類型擦除
在Java中我們都知道 泛型在運(yùn)行時是被擦除的存谎!在Kt中也是如此的烹植,在運(yùn)行時候,我們沒辦法區(qū)別 List<Int>
和 List<String>
但是 kt 中提供了一種有局限的解決方法愕贡!通過inline
和reified
兩個關(guān)鍵字草雕,來支持真泛型函數(shù)!但是也是有局限的固以!
// 我們 使用了內(nèi)聯(lián) 和真泛型
inline fun <reified T> isInstance(obj: Any?): Boolean {
if (obj == null) return false
return obj is T
}
fun main(args: Array<String>) {
val obj = "kt"
val isString = isInstance<String>(obj)
println(isString)
}
這里的泛型可以做如下的事情:
- 在類型檢查和類型轉(zhuǎn)換中使用 (
is
,!is
,as
,as?
) - 可以獲取到 KClass , 通過
T::class
可以獲取KClass墩虹,當(dāng)然獲取了Kt的KClass,也就能相應(yīng)的獲取到 Java 的Class
對象了 - 可以作為調(diào)用其他泛型函數(shù)的憨琳,類型實(shí)參
不能做以下的事情:
- 創(chuàng)建類型參數(shù)對應(yīng)的類的實(shí)例
- 調(diào)用類型的伴生對象的方法
- 把類诫钓,屬性,和非內(nèi)聯(lián)的設(shè)置成
reified
協(xié)變逆變
Kt中支持泛型的變型的篙螟!可以使用 out
和 in
來定義變型的方式菌湃!out
表示協(xié)變,in
表示逆變遍略!
類和類型
什么是類惧所!什么是類型!如果在沒有泛型的類中绪杏,這很簡單下愈,一個類就是代表一種類型!但是在存在泛型的時候蕾久,我們就需要用另外的方式思考類型了势似!每一個含有泛型聲明的類!都是一個類型構(gòu)造器僧著!當(dāng)傳入不同的類型的泛型的時候履因,會構(gòu)造成不同的類型!List
接口是一個有一個泛型的類型構(gòu)造器盹愚!List<Int>
栅迄,List<String>
他們是通過List
泛型構(gòu)造器構(gòu)造出來的類型!所以有泛型的類杯拐!可以構(gòu)造出無數(shù)種類型霞篡!只要他們的泛型不同,就是不同的類型6吮啤(沒有泛型的類朗兵,也能看成是含有0個泛型的類型構(gòu)造器!)
通過泛型構(gòu)造的類型顶滩,之間的繼承關(guān)系如何呢余掖?比如List<Any>
和List<Int>
是什么關(guān)系呢?如果我們直接定義成礁鲁,兩者之間沒有任何的關(guān)系(Java就是這么做的)盐欺,有的時候我們會發(fā)現(xiàn),程序很難描述仅醇!
上圖中冗美,我們定義了 feedAll
方法 !它能喂養(yǎng)Animal
群析二,但是我們傳入一個 Cat
群的時候粉洼,發(fā)現(xiàn)編譯器是報錯的!為什么叶摄?因?yàn)?code>Herd<Animal>和Herd<Cat>
之間沒有任何的關(guān)系属韧,沒辦法將一個 Herd<Cat>
類型的變量傳遞給Herd<Animal>
類型!這很不可思議對吧蛤吓!所以說我們需要一種工具描述泛型構(gòu)造器構(gòu)造出來的類型之間的關(guān)系宵喂!
這就是我們要說的型變
!
型變分 協(xié)變
和 逆變
:
- 協(xié)變:如果 A 是 B 的父類会傲,并且通過泛型構(gòu)造器 L 锅棕,構(gòu)造的
L<A>
是L<B>
的父類!那么就說L是協(xié)變的淌山! - 逆變:通過泛型構(gòu)造器L哲戚,
L<A>
是L<B>
的子類,就說L是逆變的艾岂!
型變
的理論只發(fā)生在有泛型的類型構(gòu)造器上顺少!
我們上面的 Herd<Cat>
應(yīng)該是 Herd<Animal>
的子類才是!說我們應(yīng)該講 Herd
類設(shè)置成 協(xié)變
! class Herd<out T : Animal>
但是我們不能將所有的泛型都設(shè)置成out
或者 in
王浴,這兩者也不是亂用的脆炎!在kt中他們只能放在特定的地方!
將一個泛型設(shè)置成 out
和 in
會影響這個泛型的使用的位置氓辣!被out
修飾的泛型只能被放在out
位置秒裕!被in
修飾的只能放在in
位置!
out
位置我們指的是生產(chǎn)者位置钞啸!我們不能消費(fèi)它几蜻!
in
位置我們指的是消費(fèi)者位置喇潘,我們不能生產(chǎn)它!
如果違背了這樣的規(guī)則梭稚,實(shí)際上類型是會出現(xiàn)問題的颖低!但是在kt中編譯器能幫助你正確的使用in
和out
,用錯了會在編譯時報錯弧烤!
out 用在消費(fèi)者上
舉例上面的 Transformer
接口忱屑,如果此時 T
是out
的(協(xié)變的)!現(xiàn)有 A暇昂,B類莺戒,其中類A是類B的父類,記做A <= B
!
Transformer
是 協(xié)變的急波!則 Transformer<A> <= Transformer<B>
从铲,以下代碼:
val a : Transformer<A>
val b : Transformer<B> = Transformer<B>()
// 根據(jù)里氏替換原則,子類可以代替父類澄暮!即 b 賦值給 a
a = b
a.transform(/* 傳入的可以是 A 或者A的子類對象 */)
// 但是 最終 transform 是通過多態(tài)調(diào)用到 b 對象的食店!b對象的 transform方法只能接受B或者B的子類對象!這樣就沖突了赏寇!
如果 Transformer
是逆變的呢吉嫩?
則 Transformer<B> <= Transformer<A>
val a : Transformer<A> = Transformer<A>()
// 里氏替換原則!
val b : Transformer<B> = a
b.transform( /* 能傳入B或者B的子類 */)
// 方法也是通過多態(tài)嗅定,最終調(diào)用的是 a 對象的 `transform` 方法! 這個方法自娩,只能接受 A 或者 A的子類!我們傳入的是B或者B的子類渠退,他們也必然是A的子類忙迁,所以沒有問題!
// 但是又會出現(xiàn)新的問題碎乃!
val res : B = b.transform( /* someObj */)
// 我們調(diào)用 b 的 transform 方法姊扔!從接口層次看,我們能拿到一個 B對象梅誓,或者B對象的子類對象恰梢!但是這個方法是最終多態(tài)調(diào)用a的,a的transform 方法梗掰,返回的 A或者A的子類對象嵌言!此時我們就是把 A對象賦值給B類型了,違背了里氏替換原則<八搿(父類對象不能代替子類對象4蒈睢)
從上面的分析可以看得出,參數(shù)和返回值是不能用同一種型變的埂陆!參數(shù)是逆變的苛白,返回值是協(xié)變的娃豹!所以我們分出了
in
和out
位置,位置上的泛型购裙,不能亂用懂版!
使用點(diǎn)變型
kt中我們通過在泛型類上使用 int
和 out
關(guān)鍵字,聲明泛型的型變方式缓窜!這種聲明型變的方式我們稱為 聲明點(diǎn)變型
,在Java中我們也有型變谍咆!例如我們可以使用List<? extends Object>
來接一個 List<String>
的對象禾锤!這種叫做使用點(diǎn)變型
!
使用點(diǎn)泛型
需要在使用泛型的地方摹察,都要去使用操作符恩掷!比較的麻煩!kt這種聲明式
更加的簡單供嚎!
在kt中我們也支持使用點(diǎn)泛型
黄娘,主要的作用是,給那些克滴,沒有聲明型變方式的類提供的1普(Java中的泛型類,都是不型變的劝赔,kt支持使用點(diǎn)泛型
也是為了兼容性考慮J慕埂),你可以給沒有聲明型變方式的類着帽,在調(diào)用點(diǎn)設(shè)置in
和out
表示型變杂伟!你不可以在使用的地方聲明和類中型變方式相反的型變類型!例如kt中 List
是協(xié)變的 仍翰,你不可以什么一個List<in T>
類型赫粥!編譯器會報錯的!但是你可以聲明List<out T>
類型予借!不會報錯越平,但是是多此一舉的。
如果你在參數(shù)中灵迫,使用了使用點(diǎn)泛型
那么你也會限制這些對象的使用場景喧笔!
例如 MutableList
是不型變的:
val list: MutableList<in String> = MutableList<String>()
// 你不能用 String去接!只能用 Any? 去接!因?yàn)樗撬蓄惖母割悾?val i : String = alis[1]
val list2: MutableList<out String> = MutableList<String>()
// 編譯出錯龟再!你不能給協(xié)變的List书闸,添加任何的東西!因?yàn)槿雲(yún)⑹窃?`in` 位置利凑!
list2.add("hello")
我們說上面的 list
和 list2
不是一個常規(guī)的MutableList
浆劲,它們被稱為投影
嫌术!受限的MutableList
使用類型投影,會導(dǎo)致對象的方法調(diào)用受限制牌借!
星(*)投影
如果你不知道關(guān)于類型參數(shù)的任何信息度气!那么你可以使用 *
表示他們!
星投影使用在不同的地方膨报,有不同的含義磷籍!
-
Function<*, String>
表示Function<in Nothing, String>
; -
Function<Int, *>
表示Function<Int, out Any?>
现柠; -
Function<*, *>
表示Function<in Nothing, out Any?>
星投影院领,只能使用在,你對泛型具體類型不感興趣的地方够吩!