java 泛型學習隨筆
阿新 • • 發佈:2020-09-17
對於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"); }