日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

kotlin入门之泛型

發布時間:2023/12/20 编程问答 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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泛型具有協變性,協變就是允許上面
的賦值是合法的。

它有兩層意思:

  • 其中?是一個通配符,表示這個List的泛型類型是一個未知類型
  • extends 限制了這個未知類型的上界,也就是泛型類型必須滿足這個extends的
    限制條件
  • 這里和定義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。

    根據上面的例子,TextView是Button的父類行,也就能滿足super的限制條件,
    就可以成功賦值了。

    List<? super Button> buttons=new ArrayList<Button>(); //本身 List<? super Button> buttons=new ArrayList<TextView>(); //直接父類 List<? super Button> buttons=new ArrayList<Object>(); //間接父類

    對于使用了下界通配符的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

    var textViews:List<out TextView> var textViews:List<in TextView>

    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。

    var list: List<*>

    和 Java 不同的地方是,如果你的類型定義里已經有了out或者 in,
    那這個限制在變量聲明時也依然在,不會被*號去掉。

    比如你的類型定義里是out T : Number 的,那它加上 <*>之后的效果就不是 out Any,
    而是 out Number。

    例子:
    協變點例子

    class QueryMap<out K:CharSequence,out V:Any> {fun getKey():K =TODO()fun getValue():V =TODO() } val queryMap:QuerMap<*,*>= QueryMap<String,Int>() queryMap.getKey()//類型為CharSequence queryMap.getValue()//類型為Any

    逆變點例子

    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入门之泛型的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。