国产成人精品18p,天天干成人网,无码专区狠狠躁天天躁,美女脱精光隐私扒开免费观看

一篇文章弄懂Java和Kotlin的泛型難點(diǎn)

發(fā)布時(shí)間:2021-07-06 11:13 來(lái)源:腳本之家 閱讀:0 作者:安卓老猴子 欄目: 開(kāi)發(fā)技術(shù)

目錄

                        Java 和 Kotlin 的泛型算作是一塊挺大的知識難點(diǎn)了,涉及到很多很難理解的概念:泛型型參、泛型實(shí)參、類(lèi)型參數、不變、型變、協(xié)變、逆變、內聯(lián)等等。本篇文章就將 Java 和 Kotlin 結合著(zhù)一起講,按照我的個(gè)人理解來(lái)闡述泛型的各個(gè)知識難點(diǎn),希望對你有所幫助 😇😇

                        一、泛型類(lèi)型

                        泛型允許你定義帶類(lèi)型形參的數據類(lèi)型,當這種類(lèi)型的實(shí)例被創(chuàng )建出來(lái)后,類(lèi)型形參便被替換為稱(chēng)為類(lèi)型實(shí)參的具體類(lèi)型。例如,對于 List<T>,List 稱(chēng)為基礎類(lèi)型,T 便是類(lèi)型型參,T 可以是任意類(lèi)型,當沒(méi)有指定 T 的具體類(lèi)型時(shí),我們只能知道List<T>是一個(gè)集合列表,但不知道承載的具體數據類(lèi)型。而對于 List<String>,當中的 String 便是類(lèi)型實(shí)參,我們可以明白地知道該列表承載的都是字符串,在這里 String 就相當于一個(gè)參數傳遞給了 List,在這語(yǔ)義下 String 也稱(chēng)為類(lèi)型參數

                        此外,在 Kotlin 中我們可以實(shí)現實(shí)化類(lèi)型參數,在運行時(shí)的內聯(lián)函數中拿到作為類(lèi)型實(shí)參的具體類(lèi)型,即可以實(shí)現 T::class.java,但在 Java 中卻無(wú)法實(shí)現,因為內聯(lián)函數是 Kotlin 中的概念,Java 中并不存在

                        二、為什么需要泛型

                        泛型是在 Java 5 版本開(kāi)始引入的,先通過(guò)幾個(gè)小例子來(lái)明白泛型的重要性

                        以下代碼可以成功編譯,但是在運行時(shí)卻拋出了 ClassCastException。了解 ArrayList 源碼的同學(xué)就知道其內部是用一個(gè)Object[]數組來(lái)存儲數據的,這使得 ArrayList 能夠存儲任何類(lèi)型的對象,所以在沒(méi)有泛型的年代開(kāi)發(fā)者一不小心就有可能向 ArrayList 存入了非期望值,編譯期完全正常,等到在運行時(shí)就會(huì )拋出類(lèi)型轉換異常了

                        public class GenericTest {
                        
                            public static void main(String[] args) {
                                List stringList = new ArrayList();
                                addData(stringList);
                                String str = (String) stringList.get(0);
                            }
                        
                            public static void addData(List dataList) {
                                dataList.add(1);
                            }
                        
                        }
                        Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
                        

                        而有了泛型后,我們就可以寫(xiě)出更加健壯安全的代碼,以下錯誤就完全可以在編譯階段被發(fā)現,且取值的時(shí)候也不需要進(jìn)行類(lèi)型強轉

                            public static void main(String[] args) {
                                List<String> stringList = new ArrayList();
                                addData(stringList); //報錯
                                String str = stringList.get(0);
                            }
                        
                            public static void addData(List<Integer> dataList) {
                                dataList.add(1);
                            }
                        
                        

                        此外,利用泛型我們可以寫(xiě)出更加具備通用性的代碼。例如,假設我們需要從一個(gè) List 中篩選出大于 0 的全部數字,那我們自然不想為 Integer、Float、Double 等多種類(lèi)型各寫(xiě)一個(gè)篩選方法,此時(shí)就可以利用泛型來(lái)抽象篩選邏輯

                            public static void main(String[] args) {
                                List<Integer> integerList = new ArrayList<>();
                                integerList.add(-1);
                                integerList.add(1);
                                integerList.add(2);
                                List<Integer> result1 = filter(integerList);
                        
                                List<Float> floatList = new ArrayList<>();
                                floatList.add(-1f);
                                floatList.add(1f);
                                floatList.add(2f);
                                List<Float> result2 = filter(floatList);
                            }
                        
                            public static <T extends Number> List<T> filter(List<T> data) {
                                List<T> filterList = new ArrayList<>();
                                for (T datum : data) {
                                    if (datum.doubleValue() > 0) {
                                        filterList.add(datum);
                                    }
                                }
                                return filterList;
                            }
                        
                        

                        總的來(lái)說(shuō),泛型有以下幾點(diǎn)優(yōu)勢:

                        • 類(lèi)型檢查,在編譯階段就能發(fā)現錯誤
                        • 更加語(yǔ)義化,看到 List<String>我們就知道存儲的數據類(lèi)型是 String
                        • 自動(dòng)類(lèi)型轉換,在取值時(shí)無(wú)需進(jìn)行手動(dòng)類(lèi)型轉換
                        • 能夠將邏輯抽象出來(lái),使得代碼更加具有通用性

                        三、類(lèi)型擦除

                        泛型是在 Java 5 版本開(kāi)始引入的,所以在 Java 4 中 ArrayList 還不屬于泛型類(lèi),其內部通過(guò) Object 向上轉型和外部強制類(lèi)型轉換來(lái)實(shí)現數據存儲和邏輯復用,此時(shí)開(kāi)發(fā)者的項目中已經(jīng)充斥了大量以下類(lèi)型的代碼:

                        List stringList = new ArrayList();
                        stringList.add("業(yè)志陳");
                        stringList.add("https://juejin.cn/user/923245496518439");
                        String str = (String) stringList.get(0);
                        

                        而在推出泛型的同時(shí),Java 官方也必須保證二進(jìn)制的向后兼容性,用 Java 4 編譯出的 Class 文件也必須能夠在 Java 5 上正常運行,即 Java 5 必須保證以下兩種類(lèi)型的代碼能夠在 Java 5 上共存且正常運行

                        List stringList = new ArrayList();
                        List<String> stringList = new ArrayList();
                        

                        為了實(shí)現這一目的,Java 就通過(guò)類(lèi)型擦除這種比較別扭的方式來(lái)實(shí)現泛型。編譯器在編譯時(shí)會(huì )擦除類(lèi)型實(shí)參,在運行時(shí)不存在任何類(lèi)型相關(guān)的信息,泛型對于 JVM 來(lái)說(shuō)是透明的,有泛型和沒(méi)有泛型的代碼通過(guò)編譯器編譯后所生成的二進(jìn)制代碼是完全相同的

                        例如,分別聲明兩個(gè)泛型類(lèi)和非泛型類(lèi),拿到其 class 文件

                        public class GenericTest {
                        
                            public static class NodeA {
                        
                                private Object obj;
                        
                                public NodeA(Object obj) {
                                    this.obj = obj;
                                }
                        
                            }
                        
                            public static class NodeB<T> {
                        
                                private T obj;
                        
                                public NodeB(T obj) {
                                    this.obj = obj;
                                }
                        
                            }
                        
                            public static void main(String[] args) {
                                NodeA nodeA = new NodeA("業(yè)志陳");
                                NodeB<String> nodeB = new NodeB<>("業(yè)志陳");
                                System.out.println(nodeB.obj);
                            }
                        
                        }
                        
                        

                        可以看到 NodeA 和 NodeB 兩個(gè)對象對應的字節碼其實(shí)是完全一樣的,最終都是使用 Object 來(lái)承載數據,就好像傳遞給 NodeB 的類(lèi)型參數 String 不見(jiàn)了一樣,這便是類(lèi)型擦除

                        public class generic.GenericTest {
                          public generic.GenericTest();
                            Code:
                               0: aload_0
                               1: invokespecial #1                  // Method java/lang/Object."<init>":()V
                               4: return
                        
                          public static void main(java.lang.String[]);
                            Code:
                               0: new           #2                  // class generic/GenericTest$NodeA
                               3: dup
                               4: ldc           #3                  // String 業(yè)志陳
                               6: invokespecial #4                  // Method generic/GenericTest$NodeA."<init>":(Ljava/lang/Object;)V
                               9: astore_1
                              10: new           #5                  // class generic/GenericTest$NodeB
                              13: dup
                              14: ldc           #3                  // String 業(yè)志陳
                              16: invokespecial #6                  // Method generic/GenericTest$NodeB."<init>":(Ljava/lang/Object;)V
                              19: astore_2
                              20: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
                              23: aload_2
                              24: invokestatic  #8                  // Method generic/GenericTest$NodeB.access$000:(Lgeneric/GenericTest$NodeB;)Ljava/lang/Object;
                              27: checkcast     #9                  // class java/lang/String
                              30: invokevirtual #10                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
                              33: return
                        }
                        
                        

                        而如果讓 NodeA 直接使用 String 類(lèi)型,并且為泛型類(lèi) NodeB 設定上界約束 String,兩者的字節碼也會(huì )完全一樣

                        public class GenericTest {
                        
                            public static class NodeA {
                        
                                private String obj;
                        
                                public NodeA(String obj) {
                                    this.obj = obj;
                                }
                        
                            }
                        
                            public static class NodeB<T extends String> {
                        
                                private T obj;
                        
                                public NodeB(T obj) {
                                    this.obj = obj;
                                }
                        
                            }
                        
                            public static void main(String[] args) {
                                NodeA nodeA = new NodeA("業(yè)志陳");
                                NodeB<String> nodeB = new NodeB<>("業(yè)志陳");
                                System.out.println(nodeB.obj);
                            }
                        
                        }
                        
                        

                        可以看到 NodeA 和 NodeB 的字節碼是完全相同的

                        public class generic.GenericTest {
                          public generic.GenericTest();
                            Code:
                               0: aload_0
                               1: invokespecial #1                  // Method java/lang/Object."<init>":()V
                               4: return
                        
                          public static void main(java.lang.String[]);
                            Code:
                               0: new           #2                  // class generic/GenericTest$NodeA
                               3: dup
                               4: ldc           #3                  // String 業(yè)志陳
                               6: invokespecial #4                  // Method generic/GenericTest$NodeA."<init>":(Ljava/lang/String;)V
                               9: astore_1
                              10: new           #5                  // class generic/GenericTest$NodeB
                              13: dup
                              14: ldc           #3                  // String 業(yè)志陳
                              16: invokespecial #6                  // Method generic/GenericTest$NodeB."<init>":(Ljava/lang/String;)V
                              19: astore_2
                              20: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
                              23: aload_2
                              24: invokestatic  #8                  // Method generic/GenericTest$NodeB.access$000:(Lgeneric/GenericTest$NodeB;)Ljava/lang/String;
                              27: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
                              30: return
                        }
                        
                        

                        所以說(shuō),當泛型類(lèi)型被擦除后有兩種轉換方式

                        • 如果泛型沒(méi)有設置上界約束,那么將泛型轉化成 Object 類(lèi)型
                        • 如果泛型設置了上界約束,那么將泛型轉化成該上界約束

                        該結論也可以通過(guò)反射泛型類(lèi)的 Class 對象來(lái)驗證

                        public class GenericTest {
                        
                            public static class NodeA<T> {
                        
                                private T obj;
                        
                                public NodeA(T obj) {
                                    this.obj = obj;
                                }
                        
                            }
                        
                            public static class NodeB<T extends String> {
                        
                                private T obj;
                        
                                public NodeB(T obj) {
                                    this.obj = obj;
                                }
                        
                            }
                        
                            public static void main(String[] args) {
                                NodeA<String> nodeA = new NodeA<>("業(yè)志陳");
                                getField(nodeA.getClass());
                                NodeB<String> nodeB = new NodeB<>("https://juejin.cn/user/923245496518439");
                                getField(nodeB.getClass());
                            }
                        
                            private static void getField(Class clazz) {
                                for (Field field : clazz.getDeclaredFields()) {
                                    System.out.println("fieldName: " + field.getName());
                                    System.out.println("fieldTypeName: " + field.getType().getName());
                                }
                            }
                        
                        }
                        
                        

                        NodeA 對應的是 Object,NodeB 對應的是 String

                        fieldName: obj
                        fieldTypeName: java.lang.Object
                        fieldName: obj
                        fieldTypeName: java.lang.String
                        

                        那既然在運行時(shí)不存在任何類(lèi)型相關(guān)的信息,泛型又為什么能夠實(shí)現類(lèi)型檢查和類(lèi)型自動(dòng)轉換等功能呢?

                        其實(shí),類(lèi)型檢查是編譯器在編譯前幫我們完成的,編譯器知道我們聲明的具體的類(lèi)型實(shí)參,所以類(lèi)型擦除并不影響類(lèi)型檢查功能。而類(lèi)型自動(dòng)轉換其實(shí)是通過(guò)內部強制類(lèi)型轉換來(lái)實(shí)現的,上面給出的字節碼中也可以看到有一條類(lèi)型強轉 checkcast 的語(yǔ)句

                        27: checkcast     #9                  // class java/lang/String
                        

                        例如,ArrayList 內部雖然用于存儲數據的是 Object 數組,但 get 方法內部會(huì )自動(dòng)完成類(lèi)型強轉

                        transient Object[] elementData;
                        
                        public E get(int index) {
                         rangeCheck(index);
                         return elementData(index);
                        }
                        
                        @SuppressWarnings("unchecked")
                        E elementData(int index) {
                         //強制類(lèi)型轉換
                         return (E) elementData[index];
                        }
                        
                        

                        所以 Java 的泛型可以看做是一種特殊的語(yǔ)法糖,因此也被人稱(chēng)為偽泛型

                        四、類(lèi)型擦除的后遺癥

                        Java 泛型對于類(lèi)型的約束只在編譯期存在,運行時(shí)仍然會(huì )按照 Java 5 之前的機制來(lái)運行,泛型的具體類(lèi)型在運行時(shí)已經(jīng)被刪除了,所以 JVM 是識別不到我們在代碼中指定的具體的泛型類(lèi)型的

                        例如,雖然List<String>只能用于添加字符串,但我們只能泛化地識別到它屬于List<?>類(lèi)型,而無(wú)法具體判斷出該 List 內部包含的具體類(lèi)型

                        List<String> stringList = new ArrayList<>();
                        //正常
                        if (stringList instanceof ArrayList<?>) {
                        
                        }
                        //報錯
                        if (stringList instanceof ArrayList<String>) {
                        
                        }
                        
                        

                        我們只能對具體的對象實(shí)例進(jìn)行類(lèi)型校驗,但無(wú)法判斷出泛型形參的具體類(lèi)型

                        public <T> void filter(T data) {
                         //正常
                         if (data instanceof String) {
                        
                         }
                         //報錯
                         if (T instanceof String) {
                        
                         }
                         //報錯
                         Class<T> tClass = T::getClass;
                        }
                        
                        

                        此外,類(lèi)型擦除也會(huì )導致 Java 中出現多態(tài)問(wèn)題。例如,以下兩個(gè)方法的方法簽名并不完全相同,但由于類(lèi)型擦除的原因,入參參數的數據類(lèi)型都會(huì )被看成 List<Object>,從而導致兩者無(wú)法共存在同一個(gè)區域內

                        public void filter(List<String> stringList) {
                        
                        }
                        
                        public void filter(List<Integer> stringList) {
                        
                        }
                        
                        

                        五、Kotlin 泛型

                        Kotlin 泛型在大體上和 Java 一致,畢竟兩者需要保證兼容性

                        class Plate<T>(val t: T) {
                        
                            fun cut() {
                                println(t.toString())
                            }
                        
                        }
                        
                        class Apple
                        
                        class Banana
                        
                        fun main() {
                            val plateApple = Plate<Apple>(Apple())
                            //泛型類(lèi)型自動(dòng)推導
                            val plateBanana = Plate(Banana())
                            plateApple.cut()
                            plateBanana.cut()
                        }
                        
                        

                        Kotlin 也支持在擴展函數中使用泛型

                        fun <T> List<T>.find(t: T): T? {
                            val index = indexOf(t)
                            return if (index > -1) get(index) else null
                        }
                        

                        需要注意的是,為了實(shí)現向后兼容,目前高版本 Java 依然允許實(shí)例化沒(méi)有具體類(lèi)型參數的泛型類(lèi),這可以說(shuō)是一個(gè)對新版本 JDK 危險但對舊版本友好的兼容措施。但 Kotlin 要求在使用泛型時(shí)需要顯式聲明泛型類(lèi)型或者是編譯器能夠類(lèi)型推導出具體類(lèi)型,任何不具備具體泛型類(lèi)型的泛型類(lèi)都無(wú)法被實(shí)例化。因為 Kotlin 一開(kāi)始就是基于 Java 6 版本的,一開(kāi)始就存在了泛型,自然就不存在需要兼容老代碼的問(wèn)題,因此以下例子和 Java 會(huì )有不同的表現

                        val arrayList1 = ArrayList() //錯誤,編譯器報錯
                        
                        val arrayList2 = arrayListOf<Int>() //正常
                        
                        val arrayList3 = arrayListOf(1, 2, 3) //正常
                        
                        

                        還有一個(gè)比較容易讓人誤解的點(diǎn)。我們經(jīng)常會(huì )使用 as 和 as? 來(lái)進(jìn)行類(lèi)型轉換,但如果轉換對象是泛型類(lèi)型的話(huà),那就會(huì )由于類(lèi)型擦除而出現誤判。如果轉換對象有正確的基礎類(lèi)型,那么轉換就會(huì )成功,而不管類(lèi)型實(shí)參是否相符。因為在運行時(shí)轉換發(fā)生的時(shí)候類(lèi)型實(shí)參是未知的,此時(shí)編譯器只會(huì )發(fā)出 “unchecked cast” 警告,代碼還是可以正常編譯的

                        例如,在以下例子中代碼的運行結果還符合我們的預知。第一個(gè)轉換操作由于類(lèi)型相符,所以打印出了相加值。第二個(gè)轉換操作由于基礎類(lèi)型是 Set 而非 List,所以?huà)伋隽?IllegalAccessException

                        fun main() {
                            printSum(listOf(1, 2, 3)) //6
                            printSum(setOf(1, 2, 3)) //IllegalAccessException
                        }
                        
                        fun printSum(c: Collection<*>) {
                            val intList = c as? List<Int> ?: throw IllegalAccessException("List is expected")
                            println(intList.sum())
                        }
                        
                        

                        而在以下例子中拋出的卻是 ClassCastException,這是因為在運行時(shí)不會(huì )判斷且無(wú)法判斷出類(lèi)型實(shí)參到底是否是 Int,而只會(huì )判斷基礎類(lèi)型 List 是否相符,所以 as? 操作會(huì )成功,等到要執行相加操作時(shí)才會(huì )發(fā)現拿到的是 String 而非 Number

                        printSum(listOf("1", "2", "3"))
                        
                        Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Number
                        
                        

                        六、上界約束

                        泛型本身已經(jīng)帶有類(lèi)型約束的作用,我們也可以進(jìn)一步細化其支持的具體類(lèi)型

                        例如,假設存在一個(gè)盤(pán)子 Plate,我們要求該 Plate 只能用于裝水果 Fruit,那么就可以對其泛型聲明做進(jìn)一步約束,Java 中使用 extend 關(guān)鍵字來(lái)聲明約束規則,而 Kotlin 使用的是 : 。這樣 Plate 就只能用于 Fruit 和其子類(lèi),而無(wú)法用于 Noodles 等不相關(guān)的類(lèi)型,這種類(lèi)型約束就被稱(chēng)為上界約束

                        open class Fruit
                        
                        class Apple : Fruit()
                        
                        class Noodles
                        
                        class Plate<T : Fruit>(val t: T)
                        
                        fun main() {
                            val applePlate = Plate(Apple()) //正常
                            val noodlesPlate = Plate(Noodles()) //報錯
                        }
                        
                        

                        如果上界約束擁有多層類(lèi)型元素,Java 是使用 & 符號進(jìn)行鏈式聲明,Kotlin 則是用 where 關(guān)鍵字來(lái)依次進(jìn)行聲明

                        interface Soft
                        
                        class Plate<T>(val t: T) where T : Fruit, T : Soft
                        
                        open class Fruit
                        
                        class Apple : Fruit()
                        
                        class Banana : Fruit(), Soft
                        
                        fun main() {
                            val applePlate = Plate(Apple()) //報錯
                            val bananaPlate = Plate(Banana()) //正常
                        }
                        
                        

                        此外,沒(méi)有指定上界約束的類(lèi)型形參會(huì )默認使用 Any? 作為上界,即我們可以使用 String 或 String? 作為具體的類(lèi)型實(shí)參。如果想確保最終的類(lèi)型實(shí)參一定是非空類(lèi)型,那么就需要主動(dòng)聲明上界約束為 Any

                        七、類(lèi)型通配符 & 星號投影

                        假設現在有個(gè)需求,需要我們提供一個(gè)方法用于遍歷所有類(lèi)型的 List 集合并打印元素

                        第一種做法就是直接將方法參數類(lèi)型聲明為 List,不包含任何泛型類(lèi)型聲明。這種做法可行,但編譯器會(huì )警告無(wú)法確定 list元素的具體類(lèi)型,所以這不是最優(yōu)解法

                        public static void printList1(List list) {
                         for (Object o : list) {
                          System.out.println(o);
                         }
                        }
                        

                        可能會(huì )想到的第二種做法是:將泛型類(lèi)型直接聲明為 Object,希望讓其適用于任何類(lèi)型的 List。這種做法完全不可行,因為即使 String 是 Object 的子類(lèi),但 List<String> 和 List<Object>并不具備從屬關(guān)系,這導致 printList2 方法實(shí)際上只能用于List<Object>這一種具體類(lèi)型

                        public static void printList2(List<Object> list) {
                         for (Object o : list) {
                          System.out.println(o);
                         }
                        }
                        

                        最優(yōu)解法就是要用到 Java 的類(lèi)型通配符 ? 了,printList3方法完全可行且編譯器也不會(huì )警告報錯

                        public static void printList3(List<?> list) {
                         for (Object o : list) {
                          System.out.println(o);
                         }
                        }
                        

                        ? 表示我們并不關(guān)心具體的泛型類(lèi)型,而只是想配合其它類(lèi)型進(jìn)行一些條件限制。例如,printList3方法希望傳入的是一個(gè) List,但不限制泛型的具體類(lèi)型,此時(shí)List<?>就達到了這一層限制條件

                        類(lèi)型通配符也存在著(zhù)一些限制。因為 printList3 方法并不包含具體的泛型類(lèi)型,所以我們從中取出的值只能是 Object 類(lèi)型,且無(wú)法向其插入值,這都是為了避免發(fā)生 ClassCastException

                        Java 的類(lèi)型通配符對應 Kotlin 中的概念就是**星號投影 * **,Java 存在的限制在 Kotlin 中一樣有

                        fun printList(list: List<*>) {
                            for (any in list) {
                                println(any)
                            }
                        }
                        

                        此外,星號投影只能出現在類(lèi)型形參的位置,不能作為類(lèi)型實(shí)參

                        val list: MutableList<*> = ArrayList<Number>() //正常
                        
                        val list2: MutableList<*> = ArrayList<*>() //報錯
                        
                        

                        八、協(xié)變 & 不變

                        看以下例子。Apple 和 Banana 都是 Fruit 的子類(lèi),可以發(fā)現 Apple[] 類(lèi)型的對象是可以賦值給 Fruit[] 的,且 Fruit[] 可以容納 Apple 對象和 Banana 對象,這種設計就被稱(chēng)為協(xié)變,即如果 A 是 B 的子類(lèi),那么 A[] 就是 B[] 的子類(lèi)型。相對的,Object[] 就是所有數組對象的父類(lèi)型

                        static class Fruit {
                        
                        }
                        
                        static class Apple extends Fruit {
                        
                        }
                        
                        static class Banana extends Fruit {
                        
                        }
                        
                        public static void main(String[] args) {
                            Fruit[] fruitArray = new Apple[10];
                            //正常
                            fruitArray[0] = new Apple();
                            //編譯時(shí)正常,運行時(shí)拋出 ArrayStoreException
                            fruitArray[1] = new Banana();
                        }
                        
                        

                        而 Java 中的泛型是不變的,這意味著(zhù) String 雖然是 Object 的子類(lèi),但List<String>并不是List<Object>的子類(lèi)型,兩者并不具備繼承關(guān)系

                        List<String> stringList = new ArrayList<>();
                        List<Object> objectList = stringList; //報錯
                        

                        那為什么 Java 中的泛型是不變的呢?

                        這可以通過(guò)看一個(gè)例子來(lái)解釋。假設 Java 中的泛型是協(xié)變的,那么以下代碼就可以成功通過(guò)編譯階段的檢查,在運行時(shí)就不可避免地將拋出 ClassCastException,而引入泛型的初衷就是為了實(shí)現類(lèi)型安全,支持協(xié)變的話(huà)那泛型也就沒(méi)有比數組安全多少了,因此就將泛型被設計為不變的

                        List<String> strList = new ArrayList<>();
                        List<Object> objs = strList; //假設可以運行,實(shí)際上編譯器會(huì )報錯
                        objs.add(1);
                        String str = strList.get(0); //將拋出 ClassCastException,無(wú)法將整數轉換為字符串
                        

                        再來(lái)想個(gè)問(wèn)題,既然協(xié)變本身并不安全,那么數組為何又要被設計為協(xié)變呢?

                        Arrays 類(lèi)包含一個(gè) equals方法用于比較兩個(gè)數組對象是否相等。如果數組是協(xié)變的,那么就需要為每一種數組對象都定義一個(gè) equals方法,包括開(kāi)發(fā)者自定義的數據類(lèi)型。想要避免這種情況,就需要讓 Object[] 可以接收任意數組類(lèi)型,即讓 Object[] 成為所有數組對象的父類(lèi)型,這就使得數組必須支持協(xié)變,這樣多態(tài)才能生效

                        public class Arrays {
                        
                             public static boolean equals(Object[] a, Object[] a2) {
                                if (a==a2)
                                    return true;
                                if (a==null || a2==null)
                                    return false;
                        
                                int length = a.length;
                                if (a2.length != length)
                                    return false;
                        
                                for (int i=0; i<length; i++) {
                                    Object o1 = a[i];
                                    Object o2 = a2[i];
                                    if (!(o1==null ? o2==null : o1.equals(o2)))
                                        return false;
                                }
                        
                                return true;
                            }
                        
                        }
                        
                        

                        需要注意的是,Kotlin 中的數組和 Java 中的數組并不一樣,Kotlin 數組并不支持協(xié)變,Kotlin 數組類(lèi)似于集合框架,具有對應的實(shí)現類(lèi) Array,Array 屬于泛型類(lèi),支持了泛型因此也不再協(xié)變

                        val stringArray = arrayOfNulls<String>(3)
                        val anyArray: Array<Any?> = stringArray //報錯
                        

                        Java 的泛型也并非完全不變的,只是實(shí)現協(xié)變需要滿(mǎn)足一些條件,甚至也可以實(shí)現逆變,下面就來(lái)介紹下泛型如何實(shí)現協(xié)變和逆變

                        九、泛型協(xié)變

                        假設我們定義了一個(gè)copyAll希望用于 List 數據遷移。那以下操作在我們看來(lái)就是完全安全的,因為 Integer 是 Number 的子類(lèi),按道理來(lái)說(shuō)是能夠將 Integer 保存為 Number 的,但由于泛型不變性,List<Integer>并不是List<Number>的子類(lèi)型,所以實(shí)際上該操作將報錯

                            public static void main(String[] args) {
                                List<Number> numberList = new ArrayList<>();
                        
                                List<Integer> integerList = new ArrayList<>();
                                integerList.add(1);
                                integerList.add(2);
                                integerList.add(3);
                        
                                copyAll(numberList, integerList); //報錯
                            }
                        
                            private static <T> void copyAll(List<T> to, List<T> from) {
                                to.addAll(from);
                            }
                        
                        

                        思考下該操作為什么會(huì )報錯?

                        編譯器的作用之一就是進(jìn)行安全檢查并阻止可能發(fā)生不安全行為的操作,copyAll 方法會(huì )報錯,那么肯定就是編譯器覺(jué)得該方法有可能會(huì )觸發(fā)不安全的操作。開(kāi)發(fā)者的本意是希望將 Integer 類(lèi)型的數據轉移到 NumberList 中,只有這種操作且這種操作在我們看來(lái)肯定是安全的,但是編譯器不知道開(kāi)發(fā)者最終所要做的具體操作啊

                        假設 copyAll方法可以正常調用,那么copyAll方法自然只會(huì )把 from 當做 List<Number>來(lái)看待。因為 Integer 是 Number 的子類(lèi),從 integerList 獲取到的數據對于 numberList 來(lái)說(shuō)自然是安全的。而如果我們在copyAll方法中偷偷向 integerList 傳入了一個(gè) Number 類(lèi)型的值的話(huà),那么自然就將拋出異常,因為 from 實(shí)際上是 List<Integer>類(lèi)型

                        為了阻止這種不安全的行為,編譯器選擇通過(guò)直接報錯來(lái)進(jìn)行提示。為了解決報錯,我們就需要向編譯器做出安全保證:從 from 取出來(lái)的值只會(huì )當做 Number 類(lèi)型,且不會(huì )向 from 傳入任何值

                        為了達成以上保證,需要修改下 copyAll 方法

                        private static <T> void copyAll(List<T> to, List<? extends T> from) {
                         to.addAll(from);
                        }
                        

                        ? extends T 表示 from 接受 T 或者 T 的子類(lèi)型,而不單單是 T 自身,這意味著(zhù)我們可以安全地從 from 中取值并聲明為 T 類(lèi)型,但由于我們并不知道 T 代表的具體類(lèi)型,寫(xiě)入操作并不安全,因此編譯器會(huì )阻止我們向 from 執行傳值操作。有了該限制后,從integerList中取出來(lái)的值只能是當做 Number 類(lèi)型,且避免了向integerList插入非法值的可能,此時(shí)List<Integer>就相當于List<? extends Number>的子類(lèi)型了,從而使得 copyAll 方法可以正常使用

                        簡(jiǎn)而言之,帶 extends 限定了上界的通配符類(lèi)型使得泛型參數類(lèi)型是協(xié)變的,即如果 A 是 B 的子類(lèi),那么 Generic<A> 就是Generic<? extends B>的子類(lèi)型

                        十、泛型逆變

                        協(xié)變所能做到的是:如果 A 是 B 的子類(lèi),那么 Generic<A> 就是Generic<? extends B>的子類(lèi)型。逆變相反,其代表的是:如果 A 是 B 的子類(lèi),那么 Generic<B> 就是 Generic<? super A> 的子類(lèi)型

                        協(xié)變還比較好理解,畢竟其繼承關(guān)系是相同的,但逆變就比較反直覺(jué)了,整個(gè)繼承關(guān)系都倒過(guò)來(lái)了

                        逆變的作用可以通過(guò)相同的例子來(lái)理解,copyAll 方法如下修改也可以正常使用,此時(shí)就是向編譯器做出了另一種安全保證:向 numberList 傳遞的值只會(huì )是 Integer 類(lèi)型,且從 numberList 取出的值也只會(huì )當做 Object 類(lèi)型

                        private static <T> void copyAll(List<? super T> to, List<T> from) {
                         to.addAll(from);
                        }
                        

                        ? super T表示 to 接收 T 或者 T 的父類(lèi)型,而不單單是 T 自身,這意味著(zhù)我們可以安全地向 to 傳類(lèi)型為 T 的值,但由于我們并不知道 T 代表的具體類(lèi)型,所以從 to 取出來(lái)的值只能是 Object 類(lèi)型。有了該限制后,integerList只能向 numberList傳遞類(lèi)型為 Integer 的值,且避免了從 numberList 中獲取到非法類(lèi)型值的可能,此時(shí)List<Number>就相當于List<? super Integer>的子類(lèi)型了,從而使得 copyAll 方法可以正常使用

                        簡(jiǎn)而言之,帶 super 限定了下界的通配符類(lèi)型使得泛型參數類(lèi)型是逆變的,即如果 A 是 B 的子類(lèi),那么 Generic<B> 就是 Generic<? super A> 的子類(lèi)型

                        十一、out & in

                        Java 中關(guān)于泛型的困境在 Kotlin 中一樣存在,out 和 in 都是 Kotlin 的關(guān)鍵字,其作用都是為了來(lái)應對泛型問(wèn)題。in 和 out 是一個(gè)對立面,同時(shí)它們又與泛型不變相對立,統稱(chēng)為型變

                        • out 本身帶有出去的意思,本身帶有傾向于取值操作的意思,用于泛型協(xié)變
                        • in 本身帶有進(jìn)來(lái)的意思,本身帶有傾向于傳值操作的意思,用于泛型逆變

                        再來(lái)看下相同例子,該例子在 Java 中存在的問(wèn)題在 Kotlin 中一樣有

                        fun main() {
                            val numberList = mutableListOf<Number>()
                        
                            val intList = mutableListOf(1, 2, 3, 4)
                        
                            copyAll(numberList, intList) //報錯
                        
                            numberList.forEach {
                                println(it)
                            }
                        }
                        
                        fun <T> copyAll(to: MutableList<T>, from: MutableList<T>) {
                            to.addAll(from)
                        }
                        
                        

                        報錯原因和 Java 完全一樣,因為此時(shí)編譯器無(wú)法判斷出我們到底是否會(huì )做出不安全的操作,所以我們依然要來(lái)向編譯器做出安全保證

                        此時(shí)就需要在 Kotlin 中來(lái)實(shí)現泛型協(xié)變和泛型逆變了,以下兩種方式都可以實(shí)現:

                        fun <T> copyAll(to: MutableList<T>, from: MutableList<out T>) {
                            to.addAll(from)
                        }
                        
                        fun <T> copyAll(to: MutableList<in T>, from: MutableList<T>) {
                            to.addAll(from)
                        }
                        
                        

                        out 關(guān)鍵字就相當于 Java 中的<? extends T>,其作用就是限制了 from 不能用于接收值而只能向其取值,這樣就避免了從 to 取出值然后向 from 賦值這種不安全的行為了,即實(shí)現了泛型協(xié)變

                        in 關(guān)鍵字就相當于 Java 中的<? super T>,其作用就是限制了 to 只能用于接收值而不能向其取值,這樣就避免了從 to 取出值然后向 from 賦值這種不安全的行為了,即實(shí)現了泛型逆變

                        從這也可以聯(lián)想到,MutableList<*> 就相當于 MutableList<out Any?>了,兩者都帶有相同的限制條件:不允許寫(xiě)值操作,允許讀值操作,且讀取出來(lái)的值只能當做 Any?進(jìn)行處理

                        十二、支持協(xié)變的 List

                        在上述例子中,想要實(shí)現協(xié)變還有另外一種方式,那就是使用 List

                        將 from 的類(lèi)型聲明從 MutableList<T>修改為 List<T> 后,可以發(fā)現 copyAll 方法也可以正常調用了

                        fun <T> copyAll(to: MutableList<T>, from: List<T>) {
                            to.addAll(from)
                        }
                        

                        對 Kotlin 有一定了解的同學(xué)應該知道,Kotlin 中的集合框架分為兩種大類(lèi):可讀可寫(xiě)和只能讀不能寫(xiě)

                        以 Java 中的 ArrayList 為例,Kotlin 將之分為了 MutableList 和 List 兩種類(lèi)型的接口。而 List 接口中的泛型已經(jīng)使用 out 關(guān)鍵字進(jìn)行修飾了,且不包含任何傳入值并保存的方法,即 List 接口只支持讀值而不支持寫(xiě)值,其本身就已經(jīng)滿(mǎn)足了協(xié)變所需要的條件,因此copyAll 方法可以正常使用

                        public interface List<out E> : Collection<E> {
                            override val size: Int
                            override fun isEmpty(): Boolean
                            override fun contains(element: @UnsafeVariance E): Boolean
                            override fun iterator(): Iterator<E>
                            override fun containsAll(elements: Collection<@UnsafeVariance E>): Boolean
                            public operator fun get(index: Int): E
                            public fun indexOf(element: @UnsafeVariance E): Int
                            public fun lastIndexOf(element: @UnsafeVariance E): Int
                            public fun listIterator(): ListIterator<E>
                            public fun listIterator(index: Int): ListIterator<E>
                            public fun subList(fromIndex: Int, toIndex: Int): List<E>
                        }
                        

                        雖然 List 接口中有幾個(gè)方法也接收了 E 類(lèi)型的入參參數,但該方法本身不會(huì )進(jìn)行寫(xiě)值操作,所以實(shí)際上可以正常使用,Kotlin 也使用 @UnsafeVariance抑制了編譯器警告

                        十三、reified & inline

                        上文講了,由于類(lèi)型擦除,Java 和 Kotlin 的泛型類(lèi)型實(shí)參都會(huì )在編譯階段被擦除,在 Kotlin 中存在一個(gè)額外手段可以來(lái)解決這個(gè)問(wèn)題,即內聯(lián)函數

                        用關(guān)鍵字 inline 標記的函數就稱(chēng)為內聯(lián)函數,再用 reified 關(guān)鍵字修飾內聯(lián)函數中的泛型形參,編譯器在進(jìn)行編譯的時(shí)候便會(huì )將內聯(lián)函數的字節碼插入到每一個(gè)調用的地方,當中就包括泛型的類(lèi)型實(shí)參。而內聯(lián)函數的類(lèi)型形參能夠被實(shí)化,就意味著(zhù)我們可以在運行時(shí)引用實(shí)際的類(lèi)型實(shí)參了

                        例如,我們可以寫(xiě)出以下這樣的一個(gè)內聯(lián)函數,用于判斷一個(gè)對象是否是指定類(lèi)型

                        fun main() {
                            println(1.isInstanceOf<String>())
                            println("string".isInstanceOf<Int>())
                        }
                        
                        inline fun <reified T> Any.isInstanceOf(): Boolean {
                            return this is T
                        }
                        
                        

                        將以上的 Kotlin 代碼反編譯為 Java 代碼,可以看出來(lái) main()方法最終是沒(méi)有調用 isInstanceOf 方法的,具體的判斷邏輯都被插入到了main()方法內部,最終是執行了 instanceof 操作,且指定了具體的泛型類(lèi)型參數 String 和 Integer

                        public final class GenericTest6Kt {
                           public static final void main() {
                              Object $this$isInstanceOf$iv = 1;
                              int $i$f$isInstanceOf = false;
                              boolean var2 = $this$isInstanceOf$iv instanceof String;
                              $i$f$isInstanceOf = false;
                              System.out.println(var2);
                              Object $this$isInstanceOf$iv = "string";
                              $i$f$isInstanceOf = false;
                              var2 = $this$isInstanceOf$iv instanceof Integer;
                              $i$f$isInstanceOf = false;
                              System.out.println(var2);
                           }
                        
                           // $FF: synthetic method
                           public static void main(String[] var0) {
                              main();
                           }
                        
                           // $FF: synthetic method
                           public static final boolean isInstanceOf(Object $this$isInstanceOf) {
                              int $i$f$isInstanceOf = 0;
                              Intrinsics.checkNotNullParameter($this$isInstanceOf, "$this$isInstanceOf");
                              Intrinsics.reifiedOperationMarker(3, "T");
                              return $this$isInstanceOf instanceof Object;
                           }
                        }
                        
                        

                        inline 和 reified 比較有用的一個(gè)場(chǎng)景是用在 Gson 反序列的時(shí)候。由于泛型運行時(shí)類(lèi)型擦除的問(wèn)題,目前用 Gson 反序列化泛型類(lèi)時(shí)步驟是比較繁瑣的,利用 inline 和 reified 我們就可以簡(jiǎn)化很多操作

                        val gson = Gson()
                        
                        inline fun <reified T> toBean(json: String): T {
                            return gson.fromJson(json, T::class.java)
                        }
                        
                        data class BlogBean(val name: String, val url: String)
                        
                        fun main() {
                            val json = """{"name":"業(yè)志陳","url":"https://juejin.cn/user/923245496518439"}"""
                            val listJson = """[{"name":"業(yè)志陳","url":"https://juejin.cn/user/923245496518439"},{"name":"業(yè)志陳","url":"https://juejin.cn/user/923245496518439"}]"""
                        
                            val blogBean = toBean<BlogBean>(json)
                            val blogMap = toBean<Map<String, String>>(json)
                            val blogBeanList = toBean<List<BlogBean>>(listJson)
                        
                            //BlogBean(name=業(yè)志陳, url=https://juejin.cn/user/923245496518439)
                            println(blogBean)
                            //{name=業(yè)志陳, url=https://juejin.cn/user/923245496518439}
                            println(blogMap)
                            //[{name=業(yè)志陳, url=https://juejin.cn/user/923245496518439}, {name=業(yè)志陳, url=https://juejin.cn/user/923245496518439}]
                            println(blogBeanList)
                        }
                        
                        

                        我也利用 Kotlin 的這個(gè)強大特性寫(xiě)了一個(gè)用于簡(jiǎn)化 Java / Kotlin 平臺的序列化和反序列化操作的庫:JsonHolder

                        十四、總結

                        最后來(lái)做個(gè)簡(jiǎn)單的總結

                        到此這篇關(guān)于Java和Kotlin的泛型難點(diǎn)的文章就介紹到這了,更多相關(guān)Java Kotlin泛型難點(diǎn)內容請搜索腳本之家以前的文章或繼續瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

                        免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng )、來(lái)自互聯(lián)網(wǎng)轉載和分享為主,文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權請聯(lián)系QQ:712375056 進(jìn)行舉報,并提供相關(guān)證據,一經(jīng)查實(shí),將立刻刪除涉嫌侵權內容。

                        国产精品成人AV片免费看| 九九精品国产亚洲AV日韩| 久久久久亚洲AV成人无码| 成人午夜无码中文字幕| 欧美成人精品三级在线观看| 亚洲色AV性色在线观无码|