1. 程式人生 > 實用技巧 >java 泛型學習隨筆

java 泛型學習隨筆

對於java 泛型 編譯時處理,執行時擦除的特點理解

  • 對於編譯時處理
    • 在使用泛型相關的類或方法時,如果宣告時的型別和具體使用時的型別不一致則直接會編譯不通過
  • 對於執行時擦除
    • 當在執行時對兩個相同型別但泛型的具體型別不同時,在執行時實際都是操作的相同的型別,而不會體現出來泛型的的具體型別;

    

public static void main(String[] args){
    
      List a = Collections.emptyList();
        List b = Collections.emptyList();
        // true , 對於不包含泛型初始化的list實際都是使用的相同的例項資料
        LOGGER.info(String.valueOf(a == b));

        List<String> a1 = Collections.emptyList();
        boolean v = a == a1;
        // true, 對於 a 和 a1 兩個引數的型別實際是不同的,但在執行時實際是對於a和a1的型別實際都是相同的List型別; 可以通過 javap -v 類名 來檢視編譯後的class檔案
        LOGGER.info(String.valueOf(v));

        List<Integer> b1 = Collections.emptyList();
        // 由於a1和b1的泛型的具體型別不一致,因此在編譯時不會通過
//        boolean v1 = a1 == b1
}

  對於泛型的顯式限定和隱式限定區別

 public static void castQuestion() {
        // 在執行例項化操作時,實際已經隱式限定了當前物件的型別
        // 在執行具體操作時,雖然根據變數的限定符顯式定義,但在實際使用中就會丟擲錯誤
        Container<StringBuilder> stringContainer = new Container("1");
        StringBuilder element = stringContainer.getElement();

    }

    // 自定義內部容器類,型別為泛型
    // 單界限操作: E extends CharSequence,這裡限定了泛型的型別只能為CharSequence的子級
    public static class Container<E extends Serializable> {
        private E element;

        public Container(E element) {
            this.element = element;
            // 可以看到當前元素的實際型別
            System.out.println(element.getClass().getTypeName());
        }

        // 方法
        public E getElement() {
            return element;
        }

        public void setElement(E element) {
            this.element = element;
        }
    }

  可以看到在指定 new 時未顯式指定物件元素型別,但通過呼叫有參構造方法實際已限定了當前物件的element元素型別;

  雖然物件變數顯式限定了當前變數的泛型,對於操作方法實際是根據呼叫者的具體泛型型別進行限制,因此可以看到 "StringBuilder element = stringContainer.getElement();" 返回值型別為 StringBuilder;

  而由於物件中的實際型別為String型別,當將String型別強制賦值為Integer型別資料時,就會丟擲ClassCastException

由於泛型存在編譯時校驗,執行時擦寫的特點,因此為了保證執行時也提供泛型型別校驗, 在Collections中提供了 checked*的工具類,在執行操作時保證了執行時的型別校驗

    public void collectionGenericType() {
        List<Integer> integers = new ArrayList<>(Arrays.asList(1, 2, 3, 4));
        // 由於泛型存在編譯時校驗,執行時擦寫
        List noGenericTypeList = integers;
        System.out.println(noGenericTypeList == integers);
        // 雖然 noGenericTypeList 引用了 integers
        // 執行時泛型擦寫  List<Integer> -> List<Object> -> List
        // 因此可以寫入任意型別的資料
        noGenericTypeList.add("A");
        // 由於資料讀取時需要進行型別轉換(轉換為泛型的指定型別)因此會丟擲ClassCastException
//        integers.forEach(System.out::println);
        // 而對於noGenericTypeList由於沒有泛型的約束,因此讀取資料是都是按照Object型別處理
        noGenericTypeList.forEach(System.out::println);
        // 在轉換時並沒有執行型別檢查因此支援直接轉換
        List<Integer> castList = new ArrayList<>(noGenericTypeList);
        // 因此為了避免型別擦寫導致的異常,因此需要使用包裝型別工具類
        // 當轉換為checkedList時並不會進行型別校驗
        /**
         * Wrapper(裝飾器)模式的使用
         * Collections.checked*介面彌補了 泛型執行時擦寫的不足
         * 強型別: 編譯時泛型強制型別檢查,執行時利用Collections.checked*強型別檢查
         */
        List<Integer> checkedList = Collections.checkedList(castList, Integer.TYPE);
        // 會生成新的資料
        System.out.println(checkedList == castList);

        noGenericTypeList = checkedList;
        // 對於checkedList在執行新增時,會執行型別校驗,因此會直接丟擲錯誤
        noGenericTypeList.add("B");
    }