如果需要同時讀取以及寫入,那么我們就不能使用通配符了。
如何閱讀過一些Java集合類的源碼,可以發現通常我們會將兩者結合起來一起用,比如像下面這樣:
public class Collections { public static <T> void copy(List<? super T> dest, List<? extends T> src) { for (int i=0; i<src.size(); i++) dest.set(i, src.get(i)); }}- Java泛型中最令人苦惱的地方或許就是類型擦除了,特別是對于有C++經驗的程序員。類型擦除就是說Java泛型只能用于在編譯期間的靜態類型檢查,然后編譯器生成的代碼會擦除相應的類型信息,這樣到了運行期間實際上JVM根本就知道泛型所代表的具體類型。這樣做的目的是因為Java泛型是1.5之后才被引入的,為了保持向下的兼容性,所以只能做類型擦除來兼容以前的非泛型代碼。對于這一點,如果閱讀Java集合框架的源碼,可以發現有些類其實并不支持泛型。
public class Generic { static List<Apple> apples = Arrays.asList(new Apple()); static List<Fruit> fruit = Arrays.asList(new Fruit()); static class Reader<T> { T readExact(List<T> list) { return list.get(0); } } static void f1() { Reader<Fruit> fruitReader = new Reader<Fruit>(); // Errors: List<Fruit> cannot be applied to List<Apple>. // Fruit f = fruitReader.readExact(apples); } //但是按照我們通常的思維習慣,Apple和Fruit之間肯定是存在聯系,然而編譯器卻無法識別, // 那怎么在泛型代碼中解決這個問題呢?我們可以通過使用通配符來解決這個問題: static class CovariantReader<T> { T readCovariant(List<? extends T> list) { return list.get(0); } } static void f2() { CovariantReader<Fruit> fruitReader = new CovariantReader<Fruit>(); Fruit f = fruitReader.readCovariant(fruit); Fruit a = fruitReader.readCovariant(apples); } static void add() { // Wildcards allow covariance: List<? extends Fruit> flist = new ArrayList<Apple>(); // Compile Error: can't add any type of object: // flist.add(new Apple()) // flist.add(new Orange()) // flist.add(new Fruit()) // flist.add(new Object()) flist.add(null); // Legal but uninteresting // We Know that it returns at least Fruit: Fruit f = flist.get(0); //答案是否定,Java編譯器不允許我們這樣做,為什么呢? // 對于這個問題我們不妨從編譯器的角度去考慮。因為List<? extends Fruit> flist它自身可以有多種含義:// List<? extends Fruit> flist = new ArrayList<Fruit>();// List<? extends Fruit> flist = new ArrayList<Apple>();// List<? extends Fruit> flist = new ArrayList<Orange>(); } public static void main(String[] args) { f1(); f2(); } static class Fruit { } static class Apple extends Fruit { } static class Orange extends Fruit { }}4種案例public class Test2 { public static void quetion1() { //問題一:在Java中不允許創建泛型數組,類似下面這樣的做法編譯器會報錯: List<Integer>[] arrayOfLists = new List<Integer>[2]; // compile-time error //對于下面這段代碼還是很好理解,字符串數組不能存放整型元素, //而且這樣的錯誤往往要等到代碼運行的時候才能發現,編譯器是無法識別的。 Object[] strings = new String[2]; strings[0] = "hi"; // OK strings[1] = 100; // An ArrayStoreException is thrown. //由于運行時期類型信息已經被擦除,JVM實際上根本就不知道new ArrayList<String>()和new ArrayList<Integer>()的區別 Class c1 = new ArrayList<String>().getClass(); Class c2 = new ArrayList<Integer>().getClass(); System.out.println(c1.getName() + c2.getName() + (c1 == c2)); // true } //問題二(還沒理解,參看尾部的參考):繼續復用我們上面的Node的類,對于泛型代碼,Java編譯器實際上還會偷偷幫我們實現一個Bridge method。? public static void question2() { //最佳實踐,建設類型推斷,顯示設置返回類型。 } // 問題三:正如我們上面提到的,Java泛型很大程度上只能提供靜態類型檢查, // 然后類型的信息就會被擦除,所以像下面這樣利用類型參數創建實例的做法編譯器不會通過: public static <E> void append(List<E> list) { E elem = new E(); // compile-time error list.add(elem); } //但是如果某些場景我們想要需要利用類型參數創建實例,我們應該怎么做呢?可以利用反射解決這個問題: public static <E> void append(List<E> list, Class<E> cls) throws Exception { E elem = cls.newInstance(); // OK list.add(elem);// List<String> ls = new ArrayList();// append(ls, String.class); } //問題四:我們無法對泛型代碼直接使用instanceof關鍵字, //因為Java編譯器在生成代碼的時候會擦除所有相關泛型的類型信息, //正如我們上面驗證過的JVM在運行時期無法識別出ArrayList<Integer>和ArrayList<String>的之間的區別: public static <E> void rtt(List<E> list) { if (list instanceof ArrayList<Integer>) { // compile-time error // ... } } //和上面一樣,我們可以使用通配符重新設置bounds來解決這個問題: public static void rtti(List<?> list) { if (list instanceof ArrayList<?>) { // OK; instanceof requires a reifiable type // ... } } public class Node<T extends Comparable<T>> { private T data; private Node<T> next; public Node(T data, Node<T> next) { this.data = data; this.next = next; } public T getData() { return data; } // ... }}消除 unchecked cast warnings;unchecked conversion warnings.
列表優先于數組:數組提供了運行時的類型安全,但是沒有編譯時的類型安全。
Java 泛型詳解
新聞熱點
疑難解答