<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] ## 泛型擦除與實化類型 ### 泛型擦除 由于**JVM虛擬機中沒有泛型**,因此**泛型類的類型在編譯時都會被擦除**,所謂的**擦除是指當定義一個泛型時,例如`List<String>`類型,運行時它只是List,并不體現String類型**。這一點Kotlin與Java是一樣的,**泛型類實例的類型實參在運行時都會被擦除**。 接下來我們通過一個案例來解釋泛型在程序運行時會被擦除,具體代碼如下所示。 ``` fun main(args: Array<String>) { //定義一個類型為List<String>的集合 val list1 = listOf("a", "b", "c") //定義一個類型為List<Int>的集合 val list2 = listOf(1, 2, 3) //打印集合的類型 println(list1.javaClass) println(list2.javaClass) //判斷這兩個集合數據類型是否一致 println(list1.javaClass == list2.javaClass) } ``` 運行結果: ``` class java.util.Arrays$ArrayList class java.util.Arrays$ArrayList true ``` 根據運行結果可知,**List<String>和List<Int>在程序運行期間類型是一樣的,因此說明泛型在運行時都會被擦除**。 ![](https://img.kancloud.cn/16/75/1675a5129dcb4fb00eea6e03bbc4defa_949x428.png) 圖在運行時,你不會知道listl 和l ist2 是否聲明成字符串或者整數列表。它們每個都只是List。 即使編譯器看到的是兩種完全不同類型的列表,在執行的時候它們看起來卻完全一樣。盡管如此,你通常可以確信`List<String>`只包含字符串,而`List<Int>`只包含整數。因為編譯器知道類型實參,并確保每個列表中只存儲正確類型的元素(可以通過類型轉換或使用Java 原生態類型訪問列表,來欺騙編譯器,但你需要特意這樣做)。 一般而言,在is 檢查中不可能使用類型實參中的類型。下面這樣的代碼不會編譯: ``` >>> if (value is List<String>) { ... } ERROR: Cannot check for instance of erased type ``` 盡管在運行時可以完全斷定這個值是一個List ,但你依然無法判斷它是一個含有字符串的列表,還是含有人,或者含有其他什么: 這些信息被擦除了。注意擦除泛型類型信息是有好處的:應用程序使用的內存總量較小,因為要保存在內存中的類型信息更少。 ### 泛型通配符 在Java程序中,如果**不知道泛型的具體類型時**,可以用“?”通配符來代替具體的類型,而**在Kotlin中則使用“`*`”來代替泛型的具體類型,這個“`*`”就被稱為泛型通配符,它只能在“<>”中使用**。 接下來我們通過一個案例演示如何使用泛型通配符“*”,具體代碼如下所示。 ``` open class Food(val name: String) open class Flower(val name: String) class Rice : Food("大米") class Rose : Flower("玫瑰") class Container<T>(var content: T) //定義一個泛型類Container fun printInfo(container: Container<*>) { val content = container.content if (content is Food) { println(content.name) } else if (content is Flower) { println(content.name) } } fun main(args: Array<String>) { val riceContainer = Container<Rice>(Rice()) val roseContainer = Container<Rose>(Rose()) printInfo(riceContainer) printInfo(roseContainer) } ``` 運行結果: ``` 大米 玫瑰 ``` 上述代碼中,通過printInfo()方法來打印Container泛型類中傳遞的食物或者鮮花,printInfo()方法可以接收`Container<out Food>`也可以接收`Container<out Rose>`,**由于不能明確需要傳入的是什么類型,因此使用“`*`”代替**。 在main()函數中,分別創建了兩個泛型類Container的實例對象—riceContainer和roseContainer,其中riceContainer傳遞的參數類型為Rice,roseContainer傳遞的參數類型為Rose,將這兩個實例對象傳遞到printInfo()方法中即可打印運行結果。 >[success] **多學一招:星投影** **當對泛型的實參一無所知,但仍然希望用安全的方式使用它時,此時有一種安全的方式——星投影,星投影就是將泛型中的“*”等價于泛型中的注解out與in對應的協變類型參數與逆變類型參數,泛型的每個具體實例化將是該投影的子類型,Kotlin為此提供了星投影語法**,我們以自定義的泛型類`A<T>`為例來演示星投影語法,具體如下。 (1)對于泛型類`A<out T>`,其中T是一個具有上界TUpper的協變類型參數,`A<*>`等價于`A<out TUpper>`,這意味著當T未知時,可以安全地從`A<*>`中讀取TUpper的值。 (2)對于泛型類`A<in T>`,其中T是一個逆變類型參數,`A<*>`等價于`A<in Nothing>`,**由于Nothing類型表示沒有任何值,因此這意味著當T未知時,沒有安全的方式寫入`A<*>`**。 (3)對于泛型類`A<T>`,其中T是一個具有上界TUpper的**不型變類型參數**,`A<*>`在讀取值時等價于`A<out TUpper>`,而在寫值時等價于`A<in Nothing>`。 如果**泛型類型具有多個類型參數**,則**每個類型參數都可以進行單獨的星投影**,例如,如果聲明一個泛型類`B<in T,out U>`,則此時可以根據星投影語法推測出以下星投影。 > * 如果泛型類為B<*, String>,則該泛型類等價于B<in Nothing, String>。 > * 如果泛型類為B<Int,*>,則該泛型類等價于B<Int, out Any?>。 > * 如果泛型類為B<*,*>,則該泛型類等價于B<in Nothing, out Any?>。 示例:對泛型類型做類型轉換 ![](https://img.kancloud.cn/5a/9b/5a9b9261bfc0a94da8fcbd8912b59aaa_711x218.png) 編譯一切正常:編譯器只是發出了一個警告,這意味著代碼是合法的。如果在一個整型的列表或者set 上調用printSum 函數, 一切都會如預期發生:第一種情況會打印出元素之和,而第二種情況則會拋出IllegalArgumentException 。但如果你傳遞了一個錯誤類型的值,運行時會得到一個ClassCastException ![](https://img.kancloud.cn/83/49/83495ddd104787d116bdc04822c96d42_642x160.png) ### 實化類型 前面已經講過**泛型在運行時會被擦除,這樣就無法知道某一個泛型形參在使用時具體是什么類型的泛型實參**,在Java中,可以通過反射**獲取泛型的真實類型**,而**在Kotlin中,要想獲取泛型的實參類型,則需要在內聯函數(inline關鍵字定義的函數)中使用reified關鍵字修飾泛型參數才可以,這樣的參數稱為實化類型**。 >[success]**注意**:reified關鍵詞必須要和inline一起使用,因為**只有內聯的泛型函數才可以在運行時獲取泛型實參的類型**。 可能有點懵逼,待我細細道來。 由于 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) } } ~~~ 接下來我們通過一個在Any類中添加一個拓展方法isType()的案例來判斷泛型的實參類型,具體代碼如下所示。 ``` inline fun <reified T> Any.isType(): Boolean { if (this is T) { return true } return false } fun main(args: Array<String>) { println("abc".isType<String>()) println(123.isType<String>()) } ``` 運行結果: ``` true false ``` 根據運行結果可知,**如果想把泛型參數類型變為實化類型,則這個泛型參數所在的函數必須是inline函數,而且泛型參數前必須用reified關鍵字來修飾**。 接下來我們看看使用實化類型參數的一些稍微有意義的例子。一個實化類型參數能發揮作用的最簡單的例子就是標準庫函數filterisinstance 。這個函數接收一個集合,選擇其中那些指定類的實例,然后返回這些被選中的實例。下面展示了這個函數的用法。 ``` >>> val items = listOf("one", 2, "three") >>> println(items.filterIsInstance<String>()) [one, three] ``` 通過指定`<String>`作為函數的類型實參, 你表明感興趣的只是字符串。因此函數的返回類型是`List<String>`。這種情況下,類型實參在運行時是已知的, 函數filterIsInstance 使用它來檢查列表中的值是不是指定為該類型實參的類的 實例。 下面是Kotlin 標準庫函數filterIsInstance 聲明的簡化版本。 ![](https://img.kancloud.cn/fd/77/fd77baa2e99f537ce63b555a69ec214c_624x279.png) #### 為什么實化只對內聯函數有效? 這是什么原理?為什么在inline 函數中允許這樣寫`element is T `,而普通的類或函數卻不行? 正如,編譯器把實現內聯函數的字節碼插入每一次調用發生的地方。每次你調用帶實化類型參數的函數時,編譯器都知道這次特定調用中用作類型實參的確切類型。因此,編譯器可以生成引用作為類型實參的具體類的字節碼。實際上,對上面代碼清單中的`filteris instance<String>`調用來說,生成的代碼和下面這段代碼是等價的: ``` for (element in this) { if (element is String){ //引用了具體類 destination.add(element) } } ``` 因為生成的字節碼引用了具體類,而不是類型參數,它不會被運行時發生的類型參數擦除影響。 注意,帶reified 類型參數的inline 函數不能在Java 代碼中調用。普通的內聯函數可以像常規函數那樣在Java 中調用——它們可以被調用而不能被內聯。帶實化類型參數的函數需要額外的處理,來把類型實參的值替換到字節碼中,所以它們必須永遠是內聯的。這樣它們不可能用Java 那樣普通的方式調用。 一個內聯函數可以有多個實化類型參數,也可以同時擁有非實化類型參數和實化類型參數。注意, filterIsInstance 函數雖然被標記成川line ,而它并不期望lambda 作為實參。把函數標記成內聯只有在一種情況下有性能優勢,即函數擁有函數類型的形參并且其對應的實參一一lambda一一和函數一起被內聯的時候。但現在這個例子中,并不是因為性能的原因才把函數標記成inline ,這里這樣做是為了能夠使用實化類型參數。 #### 使用實化類型參數代替類引用 另一種實化類型參數的常見使用場景是為接收`java.lang.Class` 類型參數的API 構建適配器。一個這種API 的例子是JDK 中的ServiceLoader ,它接收一個代表接口或抽象類的j ava.lang . Class ,并返回實現了該接口(或繼承了該抽象類)的類的實例。現在我們看看如何利用實化類型參數更容易地調用這些API 。 通過下面的調用來使用標準的ServiceLoader Java API 加載一個服務: ``` val serviceImpl = ServiceLoader.load(Service::class.java) ``` `::class.java`的語法展現了如何獲取j ava.lang.Class 對應的Kotlin 類。這和Java 中的Service.class 是完全等同的。 現在讓我們用帶實化類型參數的函數重寫這個例子 ``` val serviceImpl = loadService<Service>() ``` 代碼是不是短了不少?要加載的服務類現在被指定成了loadService 函數的類型實參。把一個類指定成類型實參要容易理解得多,因為它的代碼比使用`::class.java`語法更短。 下面,我們看看這個loadService 函數是如何定義的: ![](https://img.kancloud.cn/e7/7d/e77df70b7cb1e42f0493506f07da4f22_615x171.png) 這種用在普通類上的`::class.java`語法也可以同樣用在實化類型參數上。使用這種語法會產生對應到指定為類型參數的類的`java.lang.Class`,你可以正常地使用它。 #### 實化類型參數的限制 盡管實化類型參數是方便的工具, 但它們也有一些限制。有一些是實化與生俱來的,而另外一些則是現有的實現決定的,而且可能在未來的Kotlin 版本中放松這些限制。 具體來說,可以按下面的方式使用實化類型參數: * 用在類型檢查和類型轉換中( is、!is 、as 、as?) * 使用Kotlin 反射API ,我們將在后面討論(::class) * 獲取相應的`java.lang.Class` (`::class.java`) * 作為調用其他函數的類型實參 不能做下面這些事情: * 創建指定為類型參數的類的實例 * 調用類型參數類的伴生對象的方法 * 調用帶實化類型參數函數的時候使用非實化類型形參作為類型實參 * 把類、屬性或者非內聯函數的類型參數標記成reified 最后一條限制會帶來有趣的后果: 因為實化類型參數只能用在內聯函數上,使用實化類型參數意味著函數和所有傳給它的lambda 都會被內聯。如果內聯函數使用lambda 的方式導致lambda 不能被內聯,或者你不想lambda 因為性能的關系被內聯,可以使用noinline修飾符把它們標記成非內聯的。
                  <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>

                              哎呀哎呀视频在线观看