1. 類型擦除
瞭解編譯器揹着我們做了什麼很重要。Java中的泛型,在編譯後會被擦除類型參數。
public static void main(String[] args) { List<Integer> integerList = new ArrayList<>(); List<String> stringList = new ArrayList<>(); Class c1 = integerList.getClass(); Class c2 = stringList.getClass(); System.out.println(c1 == c2); // true System.out.println(c1); // class java.util.ArrayList System.out.println(c2); // class java.util.ArrayList System.out.println(integerList instanceof ArrayList); // true }
注意,像下面這樣寫會報錯:
integerList instanceof ArrayList<Integer>
也就是說,我們用 instanceof 來判斷一個對象的類型的時候,只能到raw type這個層面,更進一步的類型參數是無法判斷的。
也正因爲編譯器會擦除類型信息,如下的方法重載會在編譯期報錯,因爲類型擦除後,這兩個方法的參數列表是一樣的。
void method(List<String> list){} void method(List<Integer> list){}
下面摘一段《Core Java》對Java泛型的總結
虛擬機中沒有泛型,只有普通的類和方法
所以的類型參數都用它們的限定類型替換
橋方法被合成用來保持多態
爲保證類型安全性,必要時插入強制類型轉換
題外話:編譯器在包裝類型和基本類型的轉換中也會插手,自動裝箱、自動拆箱指的就是編譯器會插入強制類型轉換的代碼。注意這是編譯期做的,不是運行期。
2. 對泛型的反射
Java中的泛型和繼承結合起來,就會涉及到協變、逆變的問題。同樣的,由於類型擦除,在用反射處理泛型類型時,處理方法又有區別。JDK1.5爲此提供了一個新的接口Type。
雖然有類型擦除,但也不是所有的地方都會被擦除,具體參見R大的這篇博客http://rednaxelafx.iteye.com/blog/586212
下面通過代碼說明在使用反射獲取泛型的綁定類型時的一種場景,以及如何在該場景下獲取綁定的具體類型。
在繼續下面的內容前,應該對Java中的ParameterizedType、TypeVariable、Class這些類型有基本的瞭解。
首先定義一個泛型類,以及一個繼承了泛型類的具體類。
public class User<T> { private String name; private int age; private T data; } class VipUser extends User<String> {}
需要注意的是,這裏User類是一個泛型,但是VipUser繼承的時候,將類型參數綁定到了String類型。
我們先用反射來處理User類。對於非泛型字段,一切正常;但是對於泛型字段,我們只能拿到類型Object。
Class<?> c = User.class; // 非泛型字段 name Field nameField = c.getDeclaredField("name"); // false System.out.println(nameField.getGenericType() instanceof ParameterizedType); // false System.out.println(nameField.getGenericType() instanceof TypeVariable); // true System.out.println(nameField.getGenericType() instanceof Class); // class java.lang.String System.out.println(nameField.getGenericType()); // 泛型字段 data Field dataField = c.getDeclaredField("data"); // true System.out.println(dataField.getGenericType() instanceof TypeVariable); // T System.out.println(dataField.getGenericType()); // class java.lang.Object System.out.println(dataField.getType());
接下來我們看看對VipUser類的處理:
Class<?> c = VipUser.class; Class<?> parent = c.getSuperclass(); Field dataField = parent.getDeclaredField("data"); // class java.lang.Object (getType方法只能拿到Object,拿不到具體類型) System.out.println(dataField.getType()); // T (仍然拿不到具體類型) System.out.println(dataField.getGenericType()); // true System.out.println(dataField.getGenericType() instanceof TypeVariable); // 通過MyBatis的TypeParameterResolver類來獲得字段類型 // class java.lang.String System.out.println(TypeParameterResolver.resolveFieldType(dataField, c));
不管是通過getGenericType方法還是getType方法,我們都拿不到定義VipUser類時綁定的String類型,但是通過MyBatis的TypeParameterResolver類,我們得到了data字段的正確類型-String。有興趣的可以參考一下MyBatis的源碼,看看是怎麼做的。
下面的代碼參考MyBatis的TypeParameterResolver類的源碼,針對上面的例子,說明如何拿到泛型綁定的具體類型。
Class<?> c = VipUser.class; // 注意是調用getGenericSuperclass方法 ParameterizedType parentAsType = (ParameterizedType) c.getGenericSuperclass(); Class<?> parentAsClass = (Class<?>) parentAsType.getRawType(); Field dataField = parentAsClass.getDeclaredField("data"); // T Type genericType = dataField.getGenericType(); // 獲取泛型的類型參數 TypeVariable[] typeVar = parentAsClass.getTypeParameters(); for (int i = 0; i < typeVar.length; i++) { if (genericType.equals(typeVar[i])) { // class java.lang.String System.out.println(parentAsType.getActualTypeArguments()[i]); break; } }
關於反射和泛型碰到一起時還有很多場景,以上給出的只是其中一種。具體的可以參考MyBatis的TypeParameterResolver類。