<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??碼云GVP開源項目 12k star Uniapp+ElementUI 功能強大 支持多語言、二開方便! 廣告
                原文章出處:[Kotlin 的泛型](https://kaixue.io/kotlin-generics/) ## 從 Kotlin 的 in 和 out 說起 這期是碼上開學 Kotlin 系列的獨立技術點部分的第一期,我們來聊一聊泛型。 提到 Kotlin 的泛型,通常離不開`in` 和`out`關鍵字,但泛型這門武功需要些基本功才能修煉,否則容易走火入魔,待筆者慢慢道來。 下面這段 Java 代碼在日常開發中應該很常見了: ~~~java ?? List<TextView> textViews = new ArrayList<TextView>(); ~~~ 其中`List<TextView>`表示這是一個泛型類型為`TextView`的`List`。 那到底什么是泛型呢?我們先來講講泛型的由來。 現在的程序開發大都是面向對象的,平時會用到各種類型的對象,一組對象通常需要用集合來存儲它們,因而就有了一些集合類,比如`List`、`Map`等。 這些集合類里面都是裝的具體類型的對象,如果每個類型都去實現諸如`TextViewList`、`ActivityList`這樣的具體的類型,顯然是不可能的。 因此就誕生了「泛型」,它的意思是把具體的類型泛化,編碼的時候用符號來指代類型,在使用的時候,再確定它的類型。 前面那個例子,`List<TextView>`就是泛型類型聲明。 既然泛型是跟類型相關的,那么是不是也能適用類型的多態呢? 先看一個常見的使用場景: ~~~java ?? TextView textView = new Button(context); // ?? 這是多態 List<Button> buttons = new ArrayList<Button>(); List<TextView> textViews = buttons; // ?? 多態用在這里會報錯 incompatible types: List<Button> cannot be converted to List<TextView> ~~~ 我們知道`Button`是繼承自`TextView`的,根據 Java 多態的特性,第一處賦值是正確的。 但是到了`List<TextView>`的時候 IDE 就報錯了,這是因為 Java 的泛型本身具有「不可變性 Invariance」,Java 里面認為`List<TextView>`和`List<Button>`類型并不一致,也就是說,子類的泛型(`List<Button>`)不屬于泛型(`List<TextView>`)的子類。 > Java 的泛型類型會在編譯時發生**類型擦除**,為了保證類型安全,不允許這樣賦值。至于什么是類型擦除,這里就不展開了。 你可以試一下,在 Java 里用數組做類似的事情,是不會報錯的,這是因為數組并沒有在編譯時擦除類型: > ~~~java > ?? > TextView[] textViews = new TextView[10]; > > ~~~ 但是在實際使用中,我們的確會有這種類似的需求,需要實現上面這種賦值。 Java 提供了「泛型通配符」`? extends`和`? super`來解決這個問題。 ## Java 中的 ? extends 在 Java 里面是這么解決的: ~~~java ?? List<Button> buttons = new ArrayList<Button>(); ?? List<? extends TextView> textViews = buttons; ~~~ 這個`? extends`叫做「上界通配符」,可以使 Java 泛型具有「協變性 Covariance」,協變就是允許上面的賦值是合法的。 > 在繼承關系樹中,子類繼承自父類,可以認為父類在上,子類在下。`extends`限制了泛型類型的父類型,所以叫上界。 它有兩層意思: * 其中`?`是個通配符,表示這個`List`的泛型類型是一個**未知類型**。 * `extends`限制了這個未知類型的上界,也就是泛型類型必須滿足這個`extends`的限制條件,這里和定義`class`的`extends`關鍵字有點不一樣: * 它的范圍不僅是所有直接和間接子類,還包括上界定義的父類本身,也就是`TextView`。 * 它還有`implements`的意思,即這里的上界也可以是`interface`。 這里`Button`是`TextView`的子類,滿足了泛型類型的限制條件,因而能夠成功賦值。 根據剛才的描述,下面幾種情況都是可以的: ~~~java ?? 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`,它的具體定義如下: ~~~java ?? public interface List<E> extends Collection<E>{ E get(int index); boolean add(E e); ... } ~~~ 上面的代碼中,`E`就是表示泛型類型的符號(用其他字母甚至單詞都可以)。 我們看看在使用了上界通配符之后,`List`的使用上有沒有什么問題: ~~~java ?? List<? extends TextView> textViews = new ArrayList<Button>(); TextView textView = textViews.get(0); // ?? get 可以 textViews.add(textView); // ?? add 會報錯,no suitable method found for add(TextView) ~~~ 前面說到`List<? extends TextView>`的泛型類型是個未知類型`?`,編譯器也不確定它是啥類型,只是有個限制條件。 由于它滿足`? extends TextView`的限制條件,所以`get`出來的對象,肯定是`TextView`的子類型,根據多態的特性,能夠賦值給`TextView`,啰嗦一句,賦值給`View`也是沒問題的。 到了`add`操作的時候,我們可以這么理解: * `List<? extends TextView>`由于類型未知,它可能是`List<Button>`,也可能是`List<TextView>`。 * 對于前者,顯然我們要添加 TextView 是不可以的。 * 實際情況是編譯器無法確定到底屬于哪一種,無法繼續執行下去,就報錯了。 那我干脆不要`extends TextView`,只用通配符`?`呢? 這樣使用`List<?>`其實是`List<? extends Object>`的縮寫。 ~~~java ?? List<Button> buttons = new ArrayList<>(); List<?> list = buttons; Object obj = list.get(0); list.add(obj); // ?? 這里還是會報錯 ~~~ 和前面的例子一樣,編譯器沒法確定`?`的類型,所以這里就只能`get`到`Object`對象。 同時編譯器為了保證類型安全,也不能向`List<?>`中添加任何類型的對象,理由同上。 由于`add`的這個限制,使用了`? extends`泛型通配符的`List`,只能夠向外提供數據被消費,從這個角度來講,向外提供數據的一方稱為「生產者 Producer」。對應的還有一個概念叫「消費者 Consumer」,對應 Java 里面另一個泛型通配符`? super`。 ## Java 中的 ? super 先看一下它的寫法: ~~~java ?? ?? List<? super Button> buttons = new ArrayList<TextView>(); ~~~ 這個`? super`叫做「下界通配符」,可以使 Java 泛型具有「逆變性 Contravariance」。 > 與上界通配符對應,這里 super 限制了通配符 ? 的子類型,所以稱之為下界。 它也有兩層意思: * 通配符`?`表示`List`的泛型類型是一個**未知類型**。 * `super`限制了這個未知類型的下界,也就是泛型類型必須滿足這個`super`的限制條件。 * `super`我們在類的方法里面經常用到,這里的范圍不僅包括`Button`的直接和間接父類,也包括下界`Button`本身。 * `super`同樣支持`interface`。 上面的例子中,`TextView`是`Button`的父類型 ,也就能夠滿足`super`的限制條件,就可以成功賦值了。 根據剛才的描述,下面幾種情況都是可以的: ~~~java ?? List<? super Button> buttons = new ArrayList<Button>(); // ?? 本身 List<? super Button> buttons = new ArrayList<TextView>(); // ?? 直接父類 List<? super Button> buttons = new ArrayList<Object>(); // ?? 間接父類 ~~~ 對于使用了下界通配符的`List`,我們再看看它的`get`和`add`操作: ~~~java ?? List<? super Button> buttons = new ArrayList<TextView>(); Object object = buttons.get(0); // ?? get 出來的是 Object 類型 Button button = ... buttons.add(button); // ?? add 操作是可以的 ~~~ 解釋下,首先`?`表示未知類型,編譯器是不確定它的類型的。 雖然不知道它的具體類型,不過在 Java 里任何對象都是`Object`的子類,所以這里能把它賦值給`Object`。 `Button`對象一定是這個未知類型的子類型,根據多態的特性,這里通過`add`添加`Button`對象是合法的。 使用下界通配符`? super`的泛型`List`,只能讀取到`Object`對象,一般沒有什么實際的使用場景,通常也只拿它來添加數據,也就是消費已有的`List<? super Button>`,往里面添加`Button`,因此這種泛型類型聲明稱之為「消費者 Consumer」。 * * * 小結下,Java 的泛型本身是不支持協變和逆變的。 * 可以使用泛型通配符`? extends`來使泛型支持協變,但是「只能讀取不能修改」,這里的修改僅指對泛型集合添加元素,如果是`remove(int index)`以及`clear`當然是可以的。 * 可以使用泛型通配符`? super`來使泛型支持逆變,但是「只能修改不能讀取」,這里說的不能讀取是指不能按照泛型類型讀取,你如果按照`Object`讀出來再強轉當然也是可以的。 根據前面的說法,這被稱為 PECS 法則:「*Producer-Extends, Consumer-Super*」。 理解了 Java 的泛型之后,再理解 Kotlin 中的泛型,有如練完九陽神功再練乾坤大挪移,就比較容易了。 ## 說回 Kotlin 中的 out 和 in 和 Java 泛型一樣,Kolin 中的泛型本身也是不可變的。 * 使用關鍵字`out`來支持協變,等同于 Java 中的上界通配符`? extends`。 * 使用關鍵字`in`來支持逆變,等同于 Java 中的下界通配符`? super`。 ~~~kotlin ??? var textViews: List<out TextView> var textViews: List<in TextView> ~~~ 換了個寫法,但作用是完全一樣的。`out`表示,我這個變量或者參數只用來輸出,不用來輸入,你只能讀我不能寫我;`in`就反過來,表示它只用來輸入,不用來輸出,你只能寫我不能讀我。 你看,我們 Android 工程師學不會`out`和`in`,其實并不是因為這兩個關鍵字多難,而是因為我們應該先學學 Java 的泛型。是吧? 說了這么多`List`,其實泛型在非集合類的使用也非常廣泛,就以「生產者-消費者」為例子: ~~~kotlin ??? class Producer<T> { fun produce(): T { ... } } val producer: Producer<out TextView> = Producer<Button>() val textView: TextView = producer.produce() // ?? 相當于 'List' 的 `get` ~~~ 再來看看消費者: ~~~kotlin ??? class Consumer<T> { fun consume(t: T) { ... } } val consumer: Consumer<in Button> = Consumer<TextView>() consumer.consume(Button(context)) // ?? 相當于 'List' 的 'add' ~~~ ## 聲明處的 out 和 in 在前面的例子中,在聲明`Producer`的時候已經確定了泛型`T`只會作為輸出來用,但是每次都需要在使用的時候加上`out TextView`來支持協變,寫起來很麻煩。 Kotlin 提供了另外一種寫法:可以在聲明類的時候,給泛型符號加上`out`關鍵字,表明泛型參數`T`只會用來輸出,在使用的時候就不用額外加`out`了。 ~~~kotlin ??? ?? class Producer<out T> { fun produce(): T { ... } } val producer: Producer<TextView> = Producer<Button>() // ?? 這里不寫 out 也不會報錯 val producer: Producer<out TextView> = Producer<Button>() // ?? out 可以但沒必要 ~~~ 與`out`一樣,可以在聲明類的時候,給泛型參數加上`in`關鍵字,來表明這個泛型參數`T`只用來輸入。 ~~~kotlin ??? ?? class Consumer<in T> { fun consume(t: T) { ... } } val consumer: Consumer<Button> = Consumer<TextView>() // ?? 這里不寫 in 也不會報錯 val consumer: Consumer<in Button> = Consumer<TextView>() // ?? in 可以但沒必要 ~~~ ## \* 號(泛型通配符) 前面講到了 Java 中單個`?`號也能作為泛型通配符使用,相當于`? extends Object`。 **它在 Kotlin 中有等效的寫法:`*`號,相當于`out Any`**。 ~~~kotlin ??? ?? var list: List<*> ~~~ 和 Java 不同的地方是,如果你的類型定義里已經有了`out`或者`in`,那這個限制在變量聲明時也依然在,不會被`*`號去掉。 比如你的類型定義里是`out T : Number`的,那它加上`<*>`之后的效果就不是`out Any`,而是`out Number`。 ## where 關鍵字(多重上界) Java 中聲明類或接口的時候,可以使用`extends`來設置邊界,將泛型類型參數限制為某個類型的子集: ~~~java ?? // ?? T 的類型必須是 Animal 的子類型 class Monster<T extends Animal>{ } ~~~ >[info]【注意】這個和前面講的聲明變量時的泛型類型聲明是不同的東西,這里并沒有`?`。 同時這個邊界是可以設置多個,用`&`符號連接: ~~~java ?? // ?? T 的類型必須同時是 Animal 和 Food 的子類型 class Monster<T extends Animal & Food>{ } ~~~ Kotlin 只是把`extends`換成了`:`冒號。 ~~~kotlin ??? ?? class Monster<T : Animal> ~~~ 設置多個邊界可以使用`where`關鍵字: ~~~kotlin ??? ?? class Monster<T> where T : Animal, T : Food ~~~ 有人在看文檔的時候覺得這個`where`是個新東西,其實雖然 Java 里沒有`where`,但它并沒有帶來新功能,只是把一個老功能換了個新寫法。 不過筆者覺得 Kotlin 里`where`這樣的寫法可讀性更符合英文里的語法,尤其是如果`Monster`本身還有繼承的時候: ~~~kotlin ??? class Monster<T> : MonsterParent<T> where T : Animal ~~~ ## reified 關鍵字 由于 Java 中的泛型存在類型擦除的情況,任何在運行時需要知道泛型確切類型信息的操作都沒法用了。Java泛型里的類型參數,也就是這個T,它不是真正的類型,只是一個代號,所以你不能把它當成一個普通的類型來用,比如你不能在方法里檢查一個對象是不是一個T的實例。這個在kotlin和Java都是一樣的。 比如你不能檢查一個對象是否為泛型類型`T`的實例: ~~~java ?? <T> void printIfTypeMatch(Object item) { if (item instanceof T) { // ?? IDE 會提示錯誤,illegal generic type for instanceof System.out.println(item); } } ~~~ Kotlin 里同樣也不行: ~~~kotlin ??? fun <T> printIfTypeMatch(item: Any) { if (item is T) { // ?? IDE 會提示錯誤,Cannot check for instance of erased type: T println(item) } } ~~~ 這個問題,在 Java 中的解決方案通常是額外傳遞一個`Class<T>`類型的參數,然后通過`Class#isInstance`方法來檢查: ~~~java ?? ?? <T> void check(Object item, Class<T> type) { if (type.isInstance(item)) { ?? System.out.println(item); } } ~~~ Kotlin 中同樣可以這么解決,不過還有一個更方便的做法:使用關鍵字`reified`配合`inline`來解決: ~~~kotlin ??? ?? ?? inline fun <reified T> printIfTypeMatch(item: Any) { if (item is T) { // ?? 這里就不會在提示錯誤了 println(item) } } ~~~ 這具體是怎么回事呢?等到后續章節講到`inline`的時候會詳細說明,這里就不過多延伸了。 還記得第二篇文章中,提到了兩個 Kotlin 泛型與 Java 泛型不一致的地方,這里作一下解答。 1. Java 里的數組是支持協變的,而 Kotlin 中的數組`Array`不支持協變。 這是因為在 Kotlin 中數組是用`Array`類來表示的,這個`Array`類使用泛型就和集合類一樣,所以不支持協變。 2. Java 中的`List`接口不支持協變,而 Kotlin 中的`List`接口支持協變。 Java 中的`List`不支持協變,原因在上文已經講過了,需要使用泛型通配符來解決。 在 Kotlin 中,實際上`MutableList`接口才相當于 Java 的`List`。Kotlin 中的`List`接口實現了只讀操作,沒有寫操作,所以不會有類型安全上的問題,自然可以支持協變。 ## 練習題 1. 實現一個`fill`函數,傳入一個`Array`和一個對象,將對象填充到`Array`中,要求`Array`參數的泛型支持逆變(假設`Array`size 為 1)。 2. 實現一個`copy`函數,傳入兩個`Array`參數,將一個`Array`中的元素復制到另外個`Array`中,要求`Array`參數的泛型分別支持協變和逆變。(提示:Kotlin 中的`for`循環如果要用索引,需要使用`Array.indices`)
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看