kotlin入门之泛型
【碼上開學】Kotlin 的泛型
在學習kotlin 泛型之前我們先來回顧一下關于Java的泛型基礎吧。
說道泛型,我們可能最常用的就是在三大集合中去使用。
泛型
將具體的類型泛化,編碼的時候用符號來值代類型,在使用時再確定他的類型。
因為泛型的存在,我們可以省去強制類型轉化。
泛型是跟類型相關的,那么是不是也能使用與類型的多態呢?
場景一:
//多態,因為Button是TextView的子類,向上轉型 TextView textView=new Button(context); List<Button> buttons=new ArrayList<Button>(); //當我們將多態使用到這里時,就發生錯誤。 List<TextView> textViews=buttons;為什么List<TextView> textViews=buttons;會報錯呢?這是因為Java的泛型本身
具有不可變性。Java里面會認為List<TextView> 和List<Button>類型不一致,
也就是說,子類的泛型(List<Button>)不屬于泛型(List<TextView> )的子類。
Java的泛型類型會在編譯時發生類型擦除,為了保證類型安全,不允許這樣賦值、
至于什么是類型擦除,等下再講。
在實際使用中,我們的確會用這種類似的需求,需要實現上面這種賦值。
Java也已經想到了,所以為我們提供了泛型通配符 ? exntends與? super
來解決這個問題
正確認識Java泛型中? exntends與? super
? exntends
List<Button> buttons=new ArrayList<Button>();List<? extends TextView> textViews=buttons;這個 ? extends 叫做 上界通配符,讓Java泛型具有協變性,協變就是允許上面
的賦值是合法的。
它有兩層意思:
限制條件
這里和定義class 的extends 關鍵字有點不一樣:
- 它的范圍不僅僅是所有直接或者間接子類,還包括上界定義的父類本身,也就是
TextView - 它還有implements的意思,即這里的上界也可以是interface.
這里的Button是TextView的子類,所以滿足泛型類型的限制條件,因而能夠成功
賦值。
以下幾種情況也可以賦值成功.
List<? extends TextView> textViews=new ArrayList<TextView>(); //本身 List<? extends TextView> textViews=new ArrayList<Button>(); //直接子類 List<? extends TextView> textViews=new ArrayList<RadioButton>(); //間接子類一般的集合類包含了get和add的兩種操作,比如Java中的List。
public interface List<E> extends Collection<E>{E get(int index);boolean add(E e); }上面代碼中的 E表示泛型類型的符號。
我們看看在使用上界通配符后,List的使用上有沒有什么問題:
List<? extends TextView> textViews=new ArrayList<Button>(); TextView textView=textViews.get(0);//get方法可以使用 textViews.add(textView);//add會報錯前面說到 List<? extends TextView> 的泛型類型是個未知類型 ?,編譯器也不確
定它是啥類型,只是有個限制條件。
由于它滿足 ? extends TextView的限制條件,所以get出來的對象,肯定是TextView
的子類。根據多態的特性,能夠賦值給TextView。
到了add操作時,我們可以理解為:
- List<? extends TextView>由于類型未知,它可能是List,也可能是
List<TextView>、List<RadioButton>。 - 對于前者,顯然我們要添加TextView是不可以的
- 實際情況是編譯器無法確定到底屬于那一種。無法繼續執行下去,就報錯了。
你可能在想那么我為什么使用通配符?呢?
其實,List<?> 相當于List<? extends Object>的縮寫。
由于 add 的這個限制,使用了 ? extends 泛型通配符的 List,只能夠向外提供數據被消費,從這個角度來講,向外提供數據的一方稱為「生產者 Producer」。對應的還有一個概念叫「消費者 Consumer」,對應 Java 里面另一個泛型通配符 ? super。
? super
List<? super Button> buttons=new ArrayList<TextView>()這個? super叫做下界通配符,可以使java泛型具有逆變性
有兩層含義:
- 通配符?表示List的泛型類型是一個未知類型
- super限制了這個未知類型的下界,也就是這個泛型類型必須滿足這個super
限制條件- super我們在類的方法里面經常用到,這里的范圍不僅包括Button的直接和間接
父類,也包括Button本身 - super同樣支持interface。
- super我們在類的方法里面經常用到,這里的范圍不僅包括Button的直接和間接
根據上面的例子,TextView是Button的父類行,也就能滿足super的限制條件,
就可以成功賦值了。
對于使用了下界通配符的List,我們在看看get和add操作:
List<? super Button> buttons=new ArrayList<TextView>(); Object object=buttons.get(0); //此處get()可以獲取,是因為Object是所有類的父類 Button button =new Button(); buttons.add(button)解釋下,首先 ? 表示未知類型,編譯器是不確定它的類型的。
雖然不知道它的具體類型,不過在 Java 里任何對象都是 Object 的子類,所以這里能把它賦值給 Object。
Button 對象一定是這個未知類型的子類型,根據多態的特性,這里通過 add 添加 Button 對象是合法的。
使用下界通配符 ? super 的泛型 List,只能讀取到 Object 對象,一般沒有什么實際的使用場景,
通常也只拿它來添加數據,也就是消費已有的 List<? super Button>,往里面添加 Button,
因此這種泛型類型聲明稱之為「消費者 Consumer」。
小結下,Java 的泛型本身是不支持協變和逆變的。
可以使用泛型通配符 ? extends 來使泛型支持協變,但是「只能讀取不能修改」,
這里的修改僅指對泛型集合添加元素,如果是 remove(int index)以及 clear當然是可以的。
可以使用泛型通配符? super 來使泛型支持逆變,但是「只能修改不能讀取」,
這里說的不能讀取是指不能按照泛型類型讀取,你如果按照 Object讀出來再強轉當然也是可以的。
說完了Java的泛型之后,我們在回頭看一下kotlin中的泛型。
kotlin 中的out和in
kotlin和java泛型一樣,kotlin中的泛型本身也是不可變的。
-使用關鍵字out來支持協變,等同于Java中的上界通配符? extends
-使用關鍵字in來支持逆變,等同于Java中的上界通配符? super
out表示,我這個變量或者參數只能用來輸出,不用來輸入,你只能讀我,不能寫我;
in表示:我只用來輸入,不用輸出,你只能寫我,不能讀我。
out
interface Bookinterface EduBook:Bookclass BookStore<out T:Book>{//此處的返回類型,則為協變點fun getBook():T{TODO()} }fun main() {val eduBookStore:BookStore<EduBook> = BookStore<EduBook>()val bookStore:BookStore<Book> =eduBookStore//我需要書,不管什么類型的,只要是書即可。所以下列兩種均可滿足val book:Book=bookStore.getBook()val book1:Book=eduBookStore.getBook()var book2:EduBook = eduBookStore.getBook()//此處錯誤,因為已經指定了需要Edu類型的書,你卻將book給我。引發錯誤var book4:EduBook = bookStore.getBook() }out 小節:
- 子類Derived兼容父類Base
- 生產者Producer<Derived>兼容Producer<Base>
- 存在協變點的類的泛型參數必須聲明為協變或者不變
- 當泛型類作為泛型參數類實例的生產者時用協變
in
//垃圾 open class Waste //干垃圾 class DryWaste : Waste(){}//垃圾桶 class Dustbin <in T:Waste>{fun put(t:T){TODO()} }fun demo(){//創建一個垃圾桶val dustbin:Dustbin<Waste> =Dustbin<Waste>()//創建一個干垃圾桶val dryWasteDustbin:Dustbin<DryWaste> = dustbin//垃圾對象val waste=Waste()//干垃圾對象val dryWaste=DryWaste()//我們的垃圾桶,可以裝我們的垃圾,以及干垃圾dustbin.put(waste)dustbin.put(dryWaste)//而干垃圾桶,只能裝干垃圾,所以下面這句話,是錯誤的。dryWasteDustbin.put(waste)dryWasteDustbin.put(dryWaste) }in 小節:
- 子類Derived兼容父類Base
- 消費者Producer<Derived>兼容Producer<Base>
- 存在逆變點的類的泛型參數必須聲明為協變或者不變
- 當泛型類作為泛型參數類實例的消費者時用協變
*號
*號
前面講到了 Java 中單個?號也能作為泛型通配符使用,相當于 ? extends Object。
它在 Kotlin 中有等效的寫法:* 號,相當于out Any。
和 Java 不同的地方是,如果你的類型定義里已經有了out或者 in,
那這個限制在變量聲明時也依然在,不會被*號去掉。
比如你的類型定義里是out T : Number 的,那它加上 <*>之后的效果就不是 out Any,
而是 out Number。
例子:
協變點例子
逆變點例子
class Function<in P1,in P2>{fun invoke(p1: P1,p2: P2)=TODO() } val f:Function<*,*>=Function<Number,Any>() f.invoke()//參數為下限,但是我們的kotlin中下限為`Nothing`,無法實例化。所以該方法的參數是傳入不了的*規則
- 如果使用在out修飾的類的泛型中使用,那么就會取其上限
- 如果使用在in修飾的類的泛型中使用,那么就會取其下限Nothing
*使用范圍
-
*不能直接或者間接應用在屬性或者函數上
- 錯誤方式:
- QueryMap<String,*>()
- maxOf<*>(1,3)
-
*適用用于作為類型描述的場景
- val querMap:QueryMap<*,*>
- if(f is Function<*,*>){...}
- HashMap<String,List<*>>(),注意:此處的List<*>,實際是value的泛型參數
泛型的概念
1.泛型是一種類型層面的抽象
2.泛型通過泛型參數實現構造更加通用的類型能力
3.泛型可以讓符合繼承關系的類型批量實現某些能力
泛型類
class List<T> {}泛型方法
fun <T> maxOf(a:T,b:T):T泛型約束
//表示 T 是Comparable的實現類 fun <T : Comparable<T>> maxOf(a:T,b:T):T{return if(a>b) a else b }多個泛型約束(where)
//表示 T 是Comparable的實現類,并且是一個返回值為Unit的方法fun <T> callMax(a:T,b:T) where T:Comparable<T>,T:() ->Unit{if (a>b) a() else b()}多個泛型參數
//該函數返回類型R必須繼承Number, T 必須實現Comparable 接口,并且是一個返回類型為R的方法fun <T,R> callMax(a:T,b:T):R where T:Comparable<T>,T:() -> R,R:Number{return if (a>b) a() else b()}內聯特化
在講解內聯特化時我們需要知道泛型的原理。
- 偽泛型:編譯時擦除類型,運行時無實際類型生成
- 例如:java、kotlin
- 真泛型:編譯時生成真實類型,運行時也存在該類
- 例如:C#、C++
我們知道JVM上的泛型,一般是通過類型擦除來實現的,所以也被成為偽泛型,也就說類型實參在運行時是不保存的。
實際上,我們可以聲明一個inline函數,使其類型實參不被擦除,但是這在Java中是不行的。
inline fun <reified T> getericMethod(t:T){//此處編譯器報錯,因為我們即使知道了這個T是什么類型,但不清楚,T有沒有無參構造器val t=T()val ts=Array<T>(3){ TODO()}val jclass=T::class.javaval list=ArrayList<T>()}實際應用
Gson中的 public <T> T fromJson(String json, Class<T> classOfT) throws JsonSyntaxException {Object object = fromJson(json, (Type) classOfT);return Primitives.wrap(classOfT).cast(object);}//我們可以簡寫為 inline fun <reified T> Gson.fromJson(json:String):T =fromJson(json,T::class.java)//使用時 val person:Person=gson.fromJson(""" {...}""") //通過類型推導 val person=gson.fromJson<Person>(""" {...}""") //泛型參數總結
以上是生活随笔為你收集整理的kotlin入门之泛型的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: myeclipse中java.lang.
- 下一篇: Android常用布局-01