本文收錄于 kotlin入門潛修專題系列,歡迎學(xué)習(xí)交流。
創(chuàng)作不易,如有轉(zhuǎn)載,還請(qǐng)備注。
泛型
如果我們了解java中的泛型,那么本篇文章提到的kotlin泛型我們也不會(huì)陌生。但是如果之前沒有接觸過泛型或者沒有真正理解泛型,本篇文章理解起來可能有些困難,不過我會(huì)盡量闡述的通俗易懂。
java中的泛型
前面一直有提到,kotlin是運(yùn)行于jvm上的語言,其對(duì)標(biāo)的語言就是java,因此我們先來講一下java的泛型,了解了java泛型的優(yōu)缺點(diǎn)之后,我們就很容易明白kotlin中泛型的設(shè)計(jì)初衷了。
首先說下泛型的概念,所謂泛型即是類型的參數(shù)化。怎么理解呢?想一下以前我們所說的方法,如果方法有入?yún)?,那么這些入?yún)⑶懊嫱鶗?huì)有類型,這個(gè)類型就是為了修飾參數(shù)所用。而假如我們?cè)趧?chuàng)建類型的時(shí)候也為其指定參數(shù),這個(gè)參數(shù)又是個(gè)類型,那么我們就稱之為泛型。
那么泛型的作用和意義是什么?使用泛型能夠像傳遞參數(shù)一樣傳遞類型,同時(shí)保證運(yùn)行時(shí)的類型安全。類型的傳遞能夠讓我們寫一份代碼就能滿足各種類型的調(diào)用;類型安全是指編譯器在編譯代碼期間會(huì)對(duì)泛型信息進(jìn)行檢查,只有符合規(guī)范的才能編譯通過,這樣可以有效避免運(yùn)行時(shí)的ClassCastException異常。這也就是和使用Object相比(所有類型都可以用基類Object表示),泛型的一個(gè)優(yōu)勢(shì)所在。泛型和Object的使用對(duì)比示例如下:
public void test(){
//使用Object的場(chǎng)景
Map map = new HashMap();
map.put("test", 1);
Integer r = (Integer)map.get("test");//正確!get返回的Object類型可以轉(zhuǎn)換為Integer。因?yàn)閙ap中存放的實(shí)際類型就是Integer類型。
String r1 = (String) map.get("test");//錯(cuò)誤!運(yùn)行時(shí)會(huì)報(bào)類型轉(zhuǎn)換異常!因?yàn)閙ap中存放的實(shí)際類型是Integer類型,而不是String。
//使用泛型
Map<String,Integer> map2 = new HashMap<String ,Integer>();
map2.put("test", 1);
Integer r3 = map2.get("test");//正確!不用考慮任何類型轉(zhuǎn)換
String r2 = map2.get("test");//編譯不通過!因?yàn)閙ap2的值只能是Integer,所以返回的是Integer,而不是String
}
java中既支持類泛型也支持方法泛型。示例如下:
public class GenericClass<T> {//創(chuàng)建類GenericClass的時(shí)候,為其指定了類型參數(shù)T。
void test(T t) {
}
}
class GenericClass2 {
<T> void test(T t) {//方法泛型化。聲明方法的時(shí)候?yàn)槠渲付祟愋蛥?shù)T。
}
}
上例簡單展示了泛型的定義,上面的T可以傳入任何類型進(jìn)行表示,這就相當(dāng)于一個(gè)入?yún)?,只不過這個(gè)入?yún)⑹莻€(gè)類型而已。
由于本章節(jié)的目的并不是為了闡述java中泛型的語法,而是想發(fā)現(xiàn)java中泛型的弊端。所以,下面我們直接使用jdk提供的泛型庫來演示下java中泛型的限制。
泛型類型是不可協(xié)變的,示例如下:
List<Integer> ints = new ArrayList<>();//正確,生成一個(gè)類型是Integer的集合
List<Object> numbers = ints;//!!!錯(cuò)誤,List<Integer>不是List<Object>的子類
按道理來講,Integer是Object的子類,如果一個(gè)集合中的元素都是Integer類型的,那么該集合顯然應(yīng)該能被放到Object集合中。然而java卻不允許我們這么做,為什么?
假如java允許這么做,那么會(huì)帶來什么后果?看下面代碼:
public static void main(String[] args) {
List<Integer> ints = new ArrayList<>();
List<Object> numbers = ints;//假如java允許這樣賦值
numbers.add("hello");//這句語法顯然是成立的,字符串屬于Object類型
Integer i = (Integer) numbers.get(0);//!!!錯(cuò)誤,這句代碼運(yùn)行的時(shí)候會(huì)拋ClassCastException異常
}
這就是為什么java不允許我們這么做的原因,就是為了保證運(yùn)行時(shí)類型安全。同時(shí)也說明了,java中的泛型不是協(xié)變(下面章節(jié)會(huì)詳細(xì)介紹什么是協(xié)變)的!
上面也說到了,這種限制其實(shí)是不合理的。但是我們?cè)賮砜聪旅嬉粋€(gè)例子:
List<Integer> ints = new ArrayList<>();
List<Object> numbers = new ArrayList<>();
numbers.addAll(ints);//!!!正確!
上面代碼中最后一句竟然是正確是寫法!可以通過addAll將Integer集合加入到Object集合!按照上面的分析,這顯然是不可能的!比如我們自己來寫一個(gè)addAll方法:
interface IList<E> {//我們定義了一個(gè)泛型接口IList
void addAll(IList<E> list);//我們定義了一個(gè)addAll方法,用于添加list集合
}
//MyList提供IList的默認(rèn)實(shí)現(xiàn)
class MyList<E> implements IList<E> {
@Override
public void addAll(IList<E> list) {
}
}
public class Main {
public static void main(String[] args) {
IList<Integer> ints = new MyList<>();
IList<Object> numbers = new MyList<>();
numbers.addAll(ints);//!!!錯(cuò)誤!
}
}
注意上面 numbers.addAll(ints)這句代碼竟然報(bào)錯(cuò)了!依然提示類型沖突!那么java list中的addAll為什么可以呢?
讓我們來看下list中的addAll方法的定義:
boolean addAll(Collection<? extends E> c);
我們發(fā)現(xiàn)addAll方法入?yún)⒌姆盒投x實(shí)際上是<?extends E>這個(gè)類型,而不是<E>這個(gè)類型。這就引出了java中的通配符(使用?表示)概念。
著名的PECS法則
上一章節(jié)中引出了java中通配符的概念,java中的通配符可分為三類:
- 無界通配符:?
- 子類限定通配符:<? extends E>
- 父類限定通配符:<? super E>
首先看下這三個(gè)通配符的使用(請(qǐng)仔細(xì)閱讀代碼中的注釋):
public class Main {
static void test(List<?> list) {
//在該方法中測(cè)試添加對(duì)象,實(shí)際上測(cè)試的是無界通配符作為類泛型參數(shù)的場(chǎng)景,因?yàn)閘ist的類型是泛型List即List<?>
list.add(null);//可以
list.add(1);//無法添加int
list.add(new Test2());//無法添加自定義Test2類型對(duì)象
list.add("test");//無法添加字符串類型
}
static void test1(List<? extends Number> list) {
//在該方法中測(cè)試添加對(duì)象,list.add實(shí)際上測(cè)試的是通配符作為類泛型參數(shù)的場(chǎng)景,因?yàn)閘ist的類型是泛型List類即List<? extends Number>
list.add(null);//可以
list.add(1);//無法添加int
list.add(new Test2());//無法添加自定義Test2類型對(duì)象
list.add("test");//無法添加字符串類型
}
static void test2(List<? super Number> list) {
//在該方法中測(cè)試添加對(duì)象,list.add實(shí)際上測(cè)試的是通配符作為類泛型參數(shù)的場(chǎng)景,因?yàn)閘ist的類型是泛型List類即List<? super Number>
list.add(null);//可以
list.add(1);//可以
list.add(new Test2());//錯(cuò)誤
list.add("test");//錯(cuò)誤
}
public static void main(String[] args) {
List list = new ArrayList();
List<Integer> list2 = new ArrayList<>();
List<Object> list3 = new ArrayList<>();
//test方法的調(diào)用,實(shí)際上測(cè)試的是無界通配符作為方法形參類型一部分的場(chǎng)景
test(list);//正確
test(list2);//正確
test(list3);//正確
//test1方法的調(diào)用,實(shí)際上測(cè)試的是子類限定通配符通配符作為方法形參類型一部分的場(chǎng)景
test1(list);//警告,沒有進(jìn)行類型檢測(cè)。傳入的是List,需要List<? extends Number>類型
test1(list2);//正確
test1(list3);//錯(cuò)誤,需要List<? extends Number>類型,但傳入的是List<Object>
//test2方法的調(diào)用,實(shí)際上測(cè)試的是父類限定通配符通配符作為方法形參類型一部分的場(chǎng)景
test2(list);//警告,沒有進(jìn)行類型檢測(cè),傳入的是List,需要List<? super Number> list類型
test2(list2);//編譯錯(cuò)誤,傳入的List<String>,然而需要的是List<? super Number>
test2(list3);//正確
}
}
上面代碼我們刻意選擇了泛型類型Number以及其子類Integer來進(jìn)行測(cè)試。注釋已經(jīng)比較詳細(xì),主要描述了通配符的應(yīng)用場(chǎng)景。結(jié)合上面代碼我們可以總結(jié)如下:
- 對(duì)于賦值操作(參數(shù)入?yún)⒁彩琴x值的一種情形)。無界通配符可以接受任意類型賦值;子類限定通配符可以接受泛型類型為其子類、本身或者沒有泛型類型的賦值,其中沒有泛型類型賦值時(shí)會(huì)有編譯警告。父類限定通配符可以接受泛型類型為其超類、本身以及沒有泛型類型的賦值,其中沒有泛型類型賦值時(shí)會(huì)有編譯警告。
- 對(duì)于讀寫操作。無界通配符無法添加除了null以外的任何對(duì)象。子類限制通配符也無法添加除了null外的任何對(duì)象,實(shí)際上子類通配符只可讀不可寫。父類限制通配符允許添加其子類,而不允許添加其父類。
總結(jié)已經(jīng)完畢,主要來看兩個(gè)點(diǎn):
- 為什么無限制通配符和子類限制通配符只有可讀性沒有可寫性?
- 為什么父類限制通配符允許子類類型寫入?
這就是我們要講的PECS原則。什么是PECS?PECS的全稱可以理解為Producer-Extends-Consumer-Super,即其描述了子類限制符和父類限制符的使用原則。
- <? extends T>子類限制符,用于生產(chǎn)者場(chǎng)景(Producer),表示可以從容器中取元素。
- <?super T>父類限制符,用于消費(fèi)者場(chǎng)景(Consumer),表示可以向容器中存入元素。
- 如果既要存元素又要取元素,那么通配符無法滿足需求。
為什么會(huì)有上面限制?其實(shí)上面已經(jīng)有所描述。這里再來闡述下。
首先,對(duì)于<? extends T>來說,表示的是T及其子類型,如果我們?cè)试S向容器中添加元素,那么我們無法確定子類型具體是什么類型,這樣在取出元素的時(shí)候就有可能報(bào)類型轉(zhuǎn)換異常,故為了運(yùn)行時(shí)安全考慮,java直接禁止了元素寫入。
對(duì)于<? super T>來說,表示的是T及其T的超類類型,如果是T的子類那么一定也是T的超類的子類,所以將子類元素添加到容器是允許的,因?yàn)槿〕鰜淼臅r(shí)候一定符合T或者T的超類類型。但是如果是T的超類那么是不允許像容器中添加元素的,因?yàn)槲覀儫o法確定T的超類具體是什么類型,取出來的時(shí)候就可能引起類型轉(zhuǎn)換錯(cuò)誤。代碼示例如下:
List<? super Number> l = new ArrayList<>();
l.add(1);//正確,Integer是Number的子類
l.add(1.1);//正確,Double是Number的子類
l.add(new Object());//錯(cuò)誤,Object不是Number的子類
List<? extends Number> l2 = new ArrayList<>();
l2.add(1);//錯(cuò)誤,子類限制通配符禁止寫
l2.add(new Object());//錯(cuò)誤,子類限制通配符禁止寫
至此,我們將java中的泛型大概過了一遍。下面來看下kotlin中的泛型。
kotlin中的泛型
聲明處變量(Declaration-site variance)
想了解聲明處變量是什么,先回到上文提到的java中的泛型問題:
//定義 一個(gè)泛型接口IList
interface IList<E> {
E getE();//只有一個(gè)getE方法,返回了E類型
}
//定義了一個(gè)test方法,該方法接收元素是String類型的集合
void test(IList<String> strs) {
IList<Object> objs = strs;//這里我們將String集合賦值于Object集合,這在java中顯然是不允許的!
}
上面test方法中的寫法在java中顯然是不允許的,如果要允許我們就必須使用通配符寫法:
void test(IList<String> strs) {
IList<? extends Object> objs = strs;//正確,這里采用了子類限制通配符寫法
}
這里問題就來了,子類限制通配符實(shí)際上是限制寫的,但這里我們并沒有寫入任何元素(IList也只有一個(gè)getE方法,只是java編譯器不知道而已),按理講不使用子類限制通配符也應(yīng)該能編譯才對(duì),然而java卻沒有通過編譯,這就是java泛型中的一個(gè)弊端。
kotlin為了解決上面問題,就引入了聲明處變量。聲明處變量的作用就是在泛型類型參數(shù)前添加特定修飾符,來保證只會(huì)返回特定元素(即PECS中的生產(chǎn)),而不會(huì)消費(fèi)任何元素(PECS中的消費(fèi))。
interface IList<out E> {//注意這里使用out修飾,這就是聲明處變量
fun getE(): E//注意這個(gè)接口只有g(shù)et方法返回了E,沒有其他任何寫入的方法。所以我們使用out修飾了IList接口
}
fun test(strs: IList<String>) {
val objs: IList<Any> = strs//正確!
}
上面就是kotlin聲明處變量的使用,解決了java在沒有消費(fèi)場(chǎng)景的時(shí)候無法賦值的問題。
這里可以這么理解,IList在<out E>修飾時(shí)是協(xié)變的,或者說E是個(gè)協(xié)變類型參數(shù);IList是E的生產(chǎn)者,而不是E的消費(fèi)者。
什么是協(xié)變?所謂協(xié)變就是只要參數(shù)類型具有繼承關(guān)系就認(rèn)為整個(gè)泛型類型也有“繼承”關(guān)系:比如上例中,String繼承于Any,那么我們就可以認(rèn)為IList<String>是IList<Any>的子類型,這樣就可以讓IList<String>類型的變量賦值于IList<Any>類型變量,這就是協(xié)變。
上面語法中的out被稱為變量注解,因?yàn)閛ut被定義在類型參數(shù)的聲明側(cè)(如IList<out E>)所以就稱為聲明處變量。這正是相對(duì)于java的“使用側(cè)變量”定義而言的(比如java想要達(dá)到這種效果,就必須要在接收處聲明為通配符泛型,而不是在IList的定義處: IList<? extends Object> objs = strs;)
對(duì)比于out修飾符,kotlin還提供了另一個(gè)修飾符:in,in修飾符和out修飾符的作用剛好相反,in修飾符主要用于生產(chǎn)者場(chǎng)景,即可以寫入。來看下面一個(gè)例子:
//這里定義另一個(gè)普通的泛型接口
interface Comparable<T> {
operator fun compareTo(other: T): Int
}
//test測(cè)試方法
fun test(x: Comparable<Number>) {
val y: Comparable<Double> = x//錯(cuò)誤!這里想要進(jìn)行寫操作,kotlin是不允許的!!!
}
那么如何解決呢?使用我們的in修飾符即可:
interface Comparable<in T> {
operator fun compareTo(other: T): Int
}
//test測(cè)試方法
fun test(x: Comparable<Number>) {
val y: Comparable<Double> = x//正確!in修飾符允許我們寫
}
這種情況叫做逆變,即我們當(dāng)類型參數(shù)具有繼承關(guān)系的時(shí)候,我們可以認(rèn)為整個(gè)泛型也有繼承關(guān)系,而使用in修飾后,可以允許父類型變量賦值于子類型變量,如上面代碼中,將Comparable<Number>類型變量x賦值給了 Comparable<Double>類型變量y,這就是逆變。
kotlin中的聲明處變量可以相對(duì)于java中的PECS理解:可簡稱為CIPO。C即是Consumer,I表示in,P表示生產(chǎn)者,O表示out。CIPO和java中的PECS一致。
類型映射(Type projections)
類型映射是屬于使用側(cè)定義的變量。先來看個(gè)例子:
//這里定義了一個(gè)數(shù)組copy的方法
fun copy(from: Array<Any>, to: Array<Any>) {
//假設(shè)這里我們就是正常完成了from元素copy到to元素中
//這顯然是合情合理的
}
fun test(){
val ints: Array<Int> = arrayOf(1, 2, 3)
val any = Array<Any>(3) { "" }
copy(ints, any)//!!!錯(cuò)誤,需要Array<Any>類型,但是傳入的是Array<Int>類型
}
上面的代碼又復(fù)現(xiàn)了經(jīng)典的問題,即泛型類型是不變因子,即Array<Int>不是Array<Any>的子類,為什么要這么限制?道理和上面一樣,kotlin認(rèn)為我們有可能會(huì)對(duì)from進(jìn)行寫操作,比如我們?cè)赾opy中為from中的一個(gè)元素賦值了一個(gè)字符串(雖然我們按正常邏輯不會(huì)這么寫,我們只需要完成copy的功能就行,但是kotlin不這么認(rèn)為)!這就會(huì)引起類型轉(zhuǎn)換異常!所以kotlin對(duì)這種情形進(jìn)行了限制。
解決方法就是禁止從from寫入,告訴編譯器我只讀取from即可!如下所示:
fun copy(from: Array<out Any>, to: Array<Any>) {//這里將from聲明為了<out Any>泛型,表示不可寫,只可讀。
}
fun test(){
val ints: Array<Int> = arrayOf(1, 2, 3)
val any = Array<Any>(3) { "" }
copy(ints, any)//正確,編譯器已經(jīng)知道from只可讀不可寫,所以允許我們這么傳入。
}
上面這種寫法就是類型映射。目的就是可以使用讀操作,而不使用寫操作。
當(dāng)然,我們也可以使用in操作符進(jìn)行修飾,表示可以使用寫操作,如下所示:
//from使用了in修飾,表示可寫,類似于java中的<? super T>
//接收String及其超類。
fun copy(from: Array<in String>, to: Array<Any>) {
}
fun test(){
val strs: Array<CharSequence> = arrayOf("1")
val any = Array<Any>(3) { "" }
copy(strs, any)//正確,CharSequence是String的超類,符合<in String>的限制
}
上面代碼需要注意的是,調(diào)用方法傳遞參數(shù)時(shí),實(shí)際上進(jìn)行的是賦值操作,這個(gè)并不是上面提到的類似于add的這種寫操作。in作用于賦值操作時(shí),只允許超類類型或自身類型賦值于其子類類型,而作用于add等寫操作時(shí),只允許寫入子類類型或者自身類型。
星號(hào)映射(Star-projections)
有些時(shí)候,我們并不知道類型參數(shù)到底是什么,但是我們依然想安全的使用這些類型參數(shù),該怎么辦?
正式基于上面的考慮,kotlin為我們提供了星號(hào)映射,其修飾符為*。
星號(hào)映射的對(duì)應(yīng)的幾種泛類型使用場(chǎng)景闡述如下(假設(shè)現(xiàn)在我們?yōu)轭怗enericClass定義了幾種泛型):
- 對(duì)于GenericClass<out T : TUpper>這種泛型來講,GenericClass<>等價(jià)于GenericClass<out TUpper>,這意味著,如果T類型是未知的,你可以安全的從GenericClass<>中讀取TUpper值。
- 對(duì)于GenericClass<in T>這種泛型來講,GenericClass<>等價(jià)于GenericClass<in Nothing>,這就意味著當(dāng)T為未知類型時(shí),你無法安全的向GenericClass<>類型中寫入任何數(shù)據(jù)。
- 對(duì)于GenericClass<T : TUpper>這種泛型來講,GenericClass<*>在讀的時(shí)候,相當(dāng)于GenericClass<out TUpper>;在寫的時(shí)候,相當(dāng)于GenericClass<in Nothing>
如果泛型有多個(gè)入?yún)㈩愋?,比?GenericClass<in T, out U>,那么星映射對(duì)應(yīng)的場(chǎng)景描述如下:
- GenericClass<*, String>等價(jià)于GenericClass<in Nothing,String>;
- GenericClass<Int, *> 等價(jià)于GenericClass<Int, out Any?>;
- GenericClass<*, *>等價(jià)于GenericClass<in Nothing, out Any?>。
emm... 上面巴拉巴拉一大堆,說的是什么玩意?
確實(shí),上面的描述枯燥難耐,很難有人能細(xì)心看下去,最敞亮的方式,還是要上幾個(gè)例子,演示下星號(hào)映射的使用場(chǎng)景。
class GenericClass<in T, out E>(t: T, val e: E) {
fun set(t: T) {
println(t)
}
fun get(): E {
return e
}
}
//測(cè)試方法,詳見注釋
fun test() {
val g1: GenericClass<*, String> = GenericClass(1, "hello")//*代替了in修飾的類型,表示In Nothing
val g2: GenericClass<Number, *> = GenericClass(1, "hello")//*代替了out修飾的類型,表示out Any?
val g3: GenericClass<*, *> = GenericClass(1, "hello")
g1.set(1)//錯(cuò)誤。由于*代替了in修飾的類型,表示in Nothing,故沒有辦法寫
val result: String = g1.get()//正確,該方法實(shí)際返回String類型
g2.set(1)//正確,in修飾的類型,可以傳入其子類型,這里為Int,繼承與Number
g2.set(Any())//錯(cuò)誤,in修飾的類型,無法傳入其超類類型,Any是Number的超類
val result2: Any? = g2.get()//由于*代替了out修飾的類型,表示out Any?可以讀出String的任意父類。換句話說,這個(gè)方法本身返回了Any?
g3.set(1)//同g1,不能寫
val result3: Any? = g3.get()//同g2
}
泛型方法
泛型的概念前面已經(jīng)介紹很多了,這里簡單演示下kotlin中泛型方法的使用:
class GenericClass<in T, out E> {
fun m1(t: T) {//可以在泛型類中定義方法,只需要方法的入?yún)⒎盒突纯伞? println(t)
}
}
class GenericTest {
companion object {
fun <T> m1(t: T) {//在普通類中定義方法,除泛型化入?yún)⒅猓€應(yīng)該在方法名前增加<T>修飾。
}
@JvmStatic fun main(args: Array<String>) {
m1(1)//調(diào)用方法
m1<Int>(1)//可以顯示指定泛型類型,但是沒有必要,直接m1(1)即可。
}
}
}
泛型約束
我們來看一個(gè)例子:
companion object {
fun <T : Comparable<T>> sort(list: List<T>) {//sort方法,入?yún)⒅荒苁荂omparable<T>子類
}
@JvmStatic
fun main(args: Array<String>) {
sort(listOf(1, 2, 3)) // 正確,Int是Comparable<Int>的子類
sort(listOf(HashMap<Int, String>())) // !!!錯(cuò)誤,HashMap<Int, String> 不是Comparable<HashMap<Int, String>>的子類
}
}
上面展示了超類限制類型的場(chǎng)景,雖然我們期望可以對(duì) HashMap<Int, String>進(jìn)行排序,但是因?yàn)镠ashMap<Int, String> 沒有實(shí)現(xiàn)Comparable<HashMap<Int, String>>接口,所以不允許調(diào)用sort方法。
在kotlin中,默認(rèn)的超類類型上限是Any?,在定義超類型的時(shí)候,只能指定一個(gè)超類,比如<T: SupperT>中只能指定T的超類上限是SupperT,而不能指定多個(gè)。但是有些時(shí)候我們確實(shí)需要指定多個(gè)超類類型,該怎么辦?
為了解決這種情況,kotlin為我們提供了where語句,示例如下:
fun <T> copyWhenGreater(list: List<T>, threshold: T): List<String>
where T : CharSequence,//T必須是CharSequence類型的子類型或者CharSequence類型
T : Comparable<T> {//同時(shí)T必須是Comparable<T>類型的子類型或者Comparable<T>類型
return list.filter { it > threshold }.map { it.toString() }
}
泛型原理
kotlin中的泛型同java一樣,都是“假”泛型,為什么這么說?是因?yàn)閗otlin中的泛型信息同java一樣,只在編譯器間有,用于編譯器做類型檢查,而在運(yùn)行的時(shí)候泛型信息就被擦除了,也就是說GenericClass<String>和GenericClass<Int>在運(yùn)行時(shí)是無差別的,等同于GenericClass<*>。
所以,我們無法在運(yùn)行時(shí)獲取任何泛型信息,也無法在運(yùn)行時(shí)做任何類型轉(zhuǎn)換檢查。比如:
fun <T : Comparable<T>> sort(list: List<T>) {
if(list is List<String>){//錯(cuò)誤,在運(yùn)行時(shí)泛型信息已經(jīng)被擦除(list的類型在運(yùn)行時(shí)都是List<*>),無法使用is進(jìn)行類型判斷
}
}
至此,我們已經(jīng)講完了kotlin中的泛型。本篇文章有點(diǎn)枯燥,體會(huì)泛型的最佳路徑還是在實(shí)踐中多用用泛型,用多了自然而然就明白了。