<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>

                企業??AI智能體構建引擎,智能編排和調試,一鍵部署,支持知識庫和私有化部署方案 廣告
                # 泛型 [TOC] >[info]注意:本篇文章,初次閱讀很難理解,建議可參考這篇文章,[泛型](https://c.lioil.win/2017/06/23/Kotlin-generics.html) 與 Java 類似,Kotlin 中的類也可以有類型參數: ```kotlin class Box<T>(t: T) { var value = t } ``` 一般來說,要創建這樣類的實例,我們需要提供類型參數: ```kotlin val box: Box<Int> = Box<Int>(1) ``` 但是**如果類型參數可以推斷出來**,例如從構造函數的參數或者從其他途徑,**允許省略類型參數**: ```kotlin val box = Box(1) // 1 具有類型 Int,所以編譯器知道我們說的是 Box<Int>。 ``` ## 型變 Java 類型系統中最棘手的部分之一是通配符類型(參見 [Java Generics FAQ](http://www.angelikalanger.com/GenericsFAQ/JavaGenericsFAQ.html))。而 Kotlin 中沒有。 相反,它有兩個其他的東西:聲明處型變(declaration-site variance)與類型投影(type projections)。 首先,讓我們思考為什么 Java 需要那些神秘的通配符。在 [《Effective Java》第三版](http://www.oracle.com/technetwork/java/effectivejava-136174.html) 解釋了該問題——第 31 條:*利用有限制通配符來提升 API 的靈活性*。 首先,Java 中的泛型是**不型變的**,這意味著 `List<String>` 并**不是** `List<Object>` 的子類型。 為什么這樣? 如果 List 不是**不型變的**,它就沒比 Java 的數組好到哪去,因為如下代碼會通過編譯然后導致運行時異常: ``` java // Java List<String> strs = new ArrayList<String>(); List<Object> objs = strs; // !!!即將來臨的問題的原因就在這里。Java 禁止這樣! objs.add(1); // 這里我們把一個整數放入一個字符串列表 String s = strs.get(0); // !!! ClassCastException:無法將整數轉換為字符串 ``` 因此,Java 禁止這樣的事情以保證運行時的安全。但這樣會有一些影響。例如,考慮 `Collection` 接口中的 `addAll()` 方法。該方法的簽名應該是什么?直覺上,我們會這樣: ``` java // Java interface Collection<E> …… { void addAll(Collection<E> items); } ``` 但隨后,我們將無法做到以下簡單的事情(這是完全安全): ``` java // Java void copyAll(Collection<Object> to, Collection<String> from) { to.addAll(from); // !!!對于這種簡單聲明的 addAll 將不能編譯: // Collection<String> 不是 Collection<Object> 的子類型 } ``` (在 Java 中,我們艱難地學到了這個教訓,參見[《Effective Java》第三版](http://www.oracle.com/technetwork/java/effectivejava-136174.html),第 28 條:*列表優先于數組*) 這就是為什么 `addAll()` 的實際簽名是以下這樣: ``` java // Java interface Collection<E> …… { void addAll(Collection<? extends E> items); } ``` **通配符類型參數** `? extends E` 表示此方法接受 `E` *或者 `E` 的 一些子類型*對象的集合,而不只是 `E` 自身。這意味著我們可以安全地從其中(該集合中的元素是 E 的子類的實例)**讀取** `E`,但**不能寫入**,因為我們不知道什么對象符合那個未知的 `E` 的子類型。反過來,該限制可以讓`Collection<String>`表示為`Collection<? extends Object>`的子類型。簡而言之,帶 **extends** 限定(**上界**)的通配符類型使得類型是**協變的(covariant)**。 理解為什么這個技巧能夠工作的關鍵相當簡單:如果只能從集合中獲取項目,那么使用 `String` 的集合, 并且從其中讀取 `Object` 也沒問題 。反過來,如果只能向集合中 _放入_ 項目,就可以用`Object` 集合并向其中放入 `String`:在 Java 中有 `List<? super String>` 是 `List<Object>` 的一個**超類**。 后者稱為**逆變性(contravariance)**,并且對于 `List <? super String>` 你只能調用接受 String 作為參數的方法(例如,你可以調用 `add(String)` 或者 `set(int, String)`),當然如果調用函數返回 `List<T>` 中的 `T`,你得到的并非一個 `String` 而是一個 `Object`。 Joshua Bloch 稱那些你只能從中**讀取**的對象為**生產者**,并稱那些你只能**寫入**的對象為**消費者**。他建議:“*為了靈活性最大化,在表示生產者或消費者的輸入參數上使用通配符類型*”,并提出了以下助記符: ***** *PECS 代表生產者-Extens,消費者-Super(Producer-Extends, Consumer-Super)。* ***** >[info]*注意*:如果你使用一個生產者對象,如 `List<? extends Foo>`,在該對象上不允許調用 `add()` 或 `set()`。但這并不意味著該對象是**不可變的**:例如,沒有什么阻止你調用 `clear()`從列表中刪除所有項目,因為 `clear()`根本無需任何參數。通配符(或其他類型的型變)保證的唯一的事情是**類型安全**。不可變性完全是另一回事。 ### 聲明處型變 假設有一個泛型接口 `Source<T>`,該接口中不存在任何以 `T` 作為參數的方法,只是方法返回 `T` 類型值: ``` java // Java interface Source<T> { T nextT(); } ``` 那么,在 `Source <Object>` 類型的變量中存儲 `Source <String>` 實例的引用是極為安全的——沒有消費者-方法可以調用。但是 Java 并不知道這一點,并且仍然禁止這樣操作: ``` java // Java void demo(Source<String> strs) { Source<Object> objects = strs; // !!!在 Java 中不允許 // …… } ``` 為了修正這一點,我們必須聲明對象的類型為 `Source<? extends Object>`,這是毫無意義的,因為我們可以像以前一樣在該對象上調用所有相同的方法,所以更復雜的類型并沒有帶來價值。但編譯器并不知道。 在 Kotlin 中,有一種方法向編譯器解釋這種情況。這稱為**聲明處型變**:我們可以標注 `Source` 的**類型參數** `T` 來確保它僅從 `Source<T>` 成員中**返回**(生產),并從不被消費。為此,我們提供 **out** 修飾符: ```kotlin interface Source<out T> { fun nextT(): T } fun demo(strs: Source<String>) { val objects: Source<Any> = strs // 這個沒問題,因為 T 是一個 out-參數 // …… } ``` 一般原則是:當一個類 `C` 的類型參數 `T` 被聲明為 **out** 時,它就只能出現在 `C` 的成員的**輸出**\-位置,但回報是 `C<Base>` 可以安全地作為`C<Derived>`的超類。 簡而言之,他們說類 `C` 是在參數 `T` 上是**協變的**,或者說 `T` 是一個**協變的**類型參數。你可以認為 `C` 是 `T` 的**生產者**,而不是 `T` 的**消費者**。 **out**修飾符稱為**型變注解**,并且由于它在類型參數聲明處提供,所以我們稱之為**聲明處型變**。這與 Java 的**使用處型變**相反,其類型用途通配符使得類型協變。 另外除了 **out**,Kotlin 又補充了一個型變注釋:**in**。它使得一個類型參數**逆變**:只可以被消費而不可以被生產。逆變類型的一個很好的例子是 `Comparable`: ```kotlin interface Comparable<in T> { operator fun compareTo(other: T): Int } fun demo(x: Comparable<Number>) { x.compareTo(1.0) // 1.0 擁有類型 Double,它是 Number 的子類型 // 因此,我們可以將 x 賦給類型為 Comparable <Double> 的變量 val y: Comparable<Double> = x // OK! } ``` 我們相信 **in** 和 **out** 兩詞是自解釋的(因為它們已經在 C# 中成功使用很長時間了),因此上面提到的助記符不是真正需要的,并且可以將其改寫為更高的目標: **[存在性(The Existential)](https://zh.wikipedia.org/wiki/%E5%AD%98%E5%9C%A8%E4%B8%BB%E4%B9%89) 轉換:消費者 in, 生產者 out\!** :-) ## 類型投影 ### 使用處型變:類型投影 將類型參數 T 聲明為 *out* 非常方便,并且能避免使用處子類型化的麻煩,但是有些類實際上**不能**限制為只返回 `T`! 一個很好的例子是 Array: ```kotlin class Array<T>(val size: Int) { fun get(index: Int): T { …… } fun set(index: Int, value: T) { …… } } ``` 該類在 `T` 上既不能是協變的也不能是逆變的。這造成了一些不靈活性。考慮下述函數: ```kotlin fun copy(from: Array<Any>, to: Array<Any>) { assert(from.size == to.size) for (i in from.indices) to[i] = from[i] } ``` 這個函數應該將項目從一個數組復制到另一個數組。讓我們嘗試在實踐中應用它: ```kotlin val ints: Array<Int> = arrayOf(1, 2, 3) val any = Array<Any>(3) { "" } copy(ints, any) // ^ 其類型為 Array<Int> 但此處期望 Array<Any> ``` 這里我們遇到同樣熟悉的問題:`Array <T>` 在 `T` 上是**不型變的**,因此 `Array <Int>` 和 `Array <Any>` 都不是另一個的子類型。為什么? 再次重復,因為 copy **可能**做壞事,也就是說,例如它可能嘗試**寫**一個 String 到 `from`,并且如果我們實際上傳遞一個 `Int` 的數組,一段時間后將會拋出一個 `ClassCastException` 異常。 那么,我們唯一要確保的是 `copy()` 不會做任何壞事。我們想阻止它**寫**到 `from`,我們可以: ```kotlin fun copy(from: Array<out Any>, to: Array<Any>) { …… } ``` 這里發生的事情稱為**類型投影**:我們說`from`不僅僅是一個數組,而是一個受限制的(**投影的**)數組:我們只可以調用返回類型為類型參數`T` 的方法,如上,這意味著我們只能調用 `get()`。這就是我們的**使用處型變**的用法,并且是對應于 Java 的 `Array<? extends Object>`、但使用更簡單些的方式。 你也可以使用 **in** 投影一個類型: ```kotlin fun fill(dest: Array<in String>, value: String) { …… } ``` `Array<in String>` 對應于 Java 的 `Array<? super String>`,也就是說,你可以傳遞一個 `CharSequence` 數組或一個 `Object` 數組給 `fill()` 函數。 ### 星投影 有時你想說,你對類型參數一無所知,但仍然希望以安全的方式使用它。 這里的安全方式是定義泛型類型的這種投影,該泛型類型的每個具體實例化將是該投影的子類型。 Kotlin 為此提供了所謂的**星投影**語法: - 對于 `Foo <out T : TUpper>`,其中 `T` 是一個具有上界 `TUpper` 的協變類型參數,`Foo <*>` 等價于 `Foo <out TUpper>`。 這意味著當 `T` 未知時,你可以安全地從 `Foo <*>` *讀取* `TUpper` 的值。 - 對于 `Foo <in T>`,其中 `T` 是一個逆變類型參數,`Foo <*>` 等價于 `Foo <in Nothing>`。 這意味著當 `T` 未知時,沒有什么可以以安全的方式*寫入* `Foo <*>`。 - 對于 `Foo <T : TUpper>`,其中 `T` 是一個具有上界 `TUpper` 的不型變類型參數,`Foo<*>` 對于讀取值時等價于 `Foo<out TUpper>` 而對于寫值時等價于 `Foo<in Nothing>`。 如果泛型類型具有多個類型參數,則每個類型參數都可以單獨投影。例如,如果類型被聲明為 `interface Function <in T, out U>`,我們可以想象以下星投影: - `Function<*, String>` 表示 `Function<in Nothing, String>`; - `Function<Int, *>` 表示 `Function<Int, out Any?>`; - `Function<*, *>` 表示 `Function<in Nothing, out Any?>`。 *注意*:星投影非常像 Java 的原始類型,但是安全。 ## 泛型函數 不僅類可以有類型參數。函數也可以有。類型參數要放在函數名稱**之前**: ```kotlin fun <T> singletonList(item: T): List<T> { // …… } fun <T> T.basicToString(): String { // 擴展函數 // …… } ``` 要調用泛型函數,在調用處函數名**之后**指定類型參數即可: ```kotlin val l = singletonList<Int>(1) ``` 可以省略能夠從上下文中推斷出來的類型參數,所以以下示例同樣適用: ```kotlin val l = singletonList(1) ``` ## 泛型約束 能夠替換給定類型參數的所有可能類型的集合可以由**泛型約束**限制。 ### 上界 最常見的約束類型是與 Java 的 *extends* 關鍵字對應的 **上界**: ```kotlin fun <T : Comparable<T>> sort(list: List<T>) { …… } ``` 冒號之后指定的類型是**上界**:只有 `Comparable<T>` 的子類型可以替代 `T`。 例如: ```kotlin sort(listOf(1, 2, 3)) // OK。Int 是 Comparable<Int> 的子類型 sort(listOf(HashMap<Int, String>())) // 錯誤:HashMap<Int, String> 不是 Comparable<HashMap<Int, String>> 的子類型 ``` 默認的上界(如果沒有聲明)是 `Any?`。在尖括號中只能指定一個上界。如果同一類型參數需要多個上界,我們需要一個單獨的 **where**-子句: ```kotlin fun <T> copyWhenGreater(list: List<T>, threshold: T): List<String> where T : CharSequence, T : Comparable<T> { return list.filter { it > threshold }.map { it.toString() } } ``` 所傳遞的類型必須同時滿足 `where` 子句的所有條件。在上述示例中,類型 `T` 必須*既*實現了 `CharSequence` *也*實現了 `Comparable`。 ## 類型擦除 Kotlin 為泛型聲明用法執行的類型安全檢測僅在編譯期進行。運行時泛型類型的實例不保留關于其類型實參的任何信息。其類型信息稱為被*擦除*。例如,`Foo<Bar>` 與 `Foo<Baz?>` 的實例都會被擦除為`Foo<*>`。 因此,并沒有通用的方法在運行時檢測一個泛型類型的實例是否通過指定類型參數所創建,并且編譯器[禁止這種 *is*檢測](http://www.kotlincn.net/docs/reference/typecasts.html#%E7%B1%BB%E5%9E%8B%E6%93%A6%E9%99%A4%E4%B8%8E%E6%B3%9B%E5%9E%8B%E6%A3%80%E6%B5%8B)。 類型轉換為帶有具體類型參數的泛型類型,如 `foo as List<String>` 無法在運行時檢測。當高級程序邏輯隱含了類型轉換的類型安全而無法直接通過編譯器推斷時,可以使用這種[非受檢類型轉換](http://www.kotlincn.net/docs/reference/typecasts.html#%E9%9D%9E%E5%8F%97%E6%A3%80%E7%B1%BB%E5%9E%8B%E8%BD%AC%E6%8D%A2)。編譯器會對非受檢類型轉換發出警告,并且在運行時只對非泛型部分檢測(相當于 `foo as List<*>`)。 泛型函數調用的類型參數也同樣只在編譯期檢測。在函數體內部,類型參數不能用于類型檢測,并且類型轉換為類型參數(`foo as T`)也是非受檢的。然而,內聯函數的[具體化的類型參數](http://www.kotlincn.net/docs/reference/inline-functions.html#%E5%85%B7%E4%BD%93%E5%8C%96%E7%9A%84%E7%B1%BB%E5%9E%8B%E5%8F%82%E6%95%B0)會由調用處內聯函數體中的類型實參所代入,因此可以用于類型檢測與轉換,與上述泛型類型的實例具有相同限制。
                  <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>

                              哎呀哎呀视频在线观看