圖片來自必應
在Java中,泛型與反射是兩個重要的概念,我們幾乎能夠經常的使用到它們。而談起Type,如果有人還比較陌生的話 ,那麼說起一個它的直接實現類——Class的話,大家都應該明白了。Type是Java語言中所有型別的公共父介面。而這篇文章,主要是講述了Type的其它四個子類——ParameterizedType、 TypeVariable、GenericArrayType、WildcardType。對想了解這幾個類的朋友一個參考
-
ParameterizedType:引數化型別
引數化型別即我們通常所說的泛型型別,一提到引數,最熟悉的就是定義方法時有形參,然後呼叫此方法時傳遞實參。那麼引數化型別怎麼理解呢?顧名思義,就是將型別由原來的具體的型別引數化,類似於方法中的變數引數,此時型別也定義成引數形式(可以稱之為型別形參),然後在使用/呼叫時傳入具體的型別(型別實參)。那麼我們的ParameterizedType就是這樣一個型別,下面我們來看看它的三個重要的方法:
-
getRawType(): Type
該方法的作用是返回當前的ParameterizedType的型別。如一個List,返回的是List的Type,即返回當前引數化型別本身的Type。
-
getOwnerType(): Type
返回ParameterizedType型別所在的類的Type。如Map.Entry<String, Object>這個引數化型別返回的事Map(因為Map.Entry這個型別所在的類是Map)的型別。
-
getActualTypeArguments(): Type[]
該方法返回引數化型別<>中的實際引數型別, 如 Map<String,Person> map 這個 ParameterizedType 返回的是 String 類,Person 類的全限定類名的 Type Array。注意: 該方法只返回最外層的<>中的型別,無論該<>內有多少個<>。
下面讓我們用一段例子來看一下具體的用法:
//是ParameterizedType private HashMap<String, Object> map; private HashSet<String> set; private List<String> list; private Class<?> clz; //不是ParameterizedType private Integer i; private String str; private static void printParameterizedType(){ Field[] fields = TestParameterizedTypeBean.class.getDeclaredFields(); for (Field f : fields){ //列印是否是ParameterizedType型別 System.out.println("FieldName: " + f.getName() + " instanceof ParameterizedType is : " + (f.getGenericType() instanceof ParameterizedType)); } //取map這個型別中的實際引數型別的陣列 getParameterizedTypeWithName("map"); getParameterizedTypeWithName("str"); } private static void getParameterizedTypeWithName(String name){ Field f; try { //利用反射得到TestParameterizedTypeBean類中的所有變數 f = TestParameterizedTypeBean.class.getDeclaredField(name); f.setAccessible(true); Type type = f.getGenericType(); if (type instanceof ParameterizedType){ for(Type param : ((ParameterizedType)type).getActualTypeArguments()){ //列印實際引數型別 System.out.println("---type actualType---" + param.toString()); } //列印所在的父類的型別 System.out.println("---type ownerType0---"+ ((ParameterizedType) type).getOwnerType()); //列印其本身的型別 System.out.println("---type rawType---"+ ((ParameterizedType) type).getRawType()); } } catch (NoSuchFieldException e) { e.printStackTrace(); } } 複製程式碼
上面的程式碼主要是定義了一些變數,這些變數中間有ParameterizedType也有普通型別變數,我們來看一下上述程式碼的輸出:
-
-
TypeVariable:型別變數
範型資訊在編譯時會被轉換為一個特定的型別, 而TypeVariable就是用來反映在JVM編譯該泛型前的資訊。(通俗的來說,TypeVariable就是我們常用的T,K這種泛型變數)
-
getBounds(): Type[]:
返回當前型別的上邊界,如果沒有指定上邊界,則預設為Object。
-
getName(): String:
返回當前型別的類名
-
getGenericDeclaration(): D
返回當前型別所在的類的Type。
下面通過一個例子來加深瞭解:
public class TestTypeVariableBean<K extends Number, T> { //K有指定了上邊界Number K key; //T沒有指定上邊界,其預設上邊界為Object T value; public static void main(String[] args){ Type[] types = TestTypeVariableBean.class.getTypeParameters(); for (Type type : types){ TypeVariable t = (TypeVariable) type; int index = t.getBounds().length - 1; //輸出上邊界 System.out.println("--getBounds()-- " + t.getBounds()[index]); //輸出名稱 System.out.println("--getName()--" + t.getName()); //輸出所在的類的型別 System.out.println("--getGenericDeclaration()--" + t.getGenericDeclaration()); } } } 複製程式碼
再來看下輸出:
-
-
GenericArrayType:泛型陣列型別:
組成陣列的元素中有泛型則實現了該介面; 它的組成元素是 ParameterizedType 或 TypeVariable 型別。(通俗來說,就是由引數型別組成的陣列。如果僅僅是引數化型別,則不能稱為泛型陣列,而是引數化型別)。注意:無論從左向右有幾個[]並列,這個方法僅僅脫去最右邊的[]之後剩下的內容就作為這個方法的返回值。
- getGenericComponentType(): Type:
返回組成泛型陣列的實際引數化型別,如List[] 則返回 List。
下面還是通過一個例子來深入瞭解:
public class TestGenericArrayTypeBean<T> { //泛型陣列型別 private T[] value; private List<String>[] list; //不是泛型陣列型別 private List<String> singleList; private T singleValue; public static void main(String[] args){ Field[] fields = TestGenericArrayTypeBean.class.getDeclaredFields(); for (Field field: fields){ field.setAccessible(true); //輸出當前變數是否為GenericArrayType型別 System.out.println("Field: " + field.getName() + "; instanceof GenericArrayType" + ": " + (field.getGenericType() instanceof GenericArrayType)); if (field.getGenericType() instanceof GenericArrayType){ //如果是GenericArrayType,則輸出當前泛型型別 System.out.println("Field: " + field.getName() + "; getGenericComponentType()" + ": " + (((GenericArrayType) field.getGenericType()).getGenericComponentType())); } } } } 複製程式碼
接下來看下輸出:
-
WildcardType: 萬用字元型別
表示萬用字元型別,比如 <?>, <? Extends Number>等
- getLowerBounds(): Type[]: 得到下邊界的陣列
- getUpperBounds(): Type[]: 得到上邊界的type陣列
注:如果沒有指定上邊界,則預設為Object,如果沒有指定下邊界,則預設為String
下面還是通過一個例子瞭解一下:
public class TestWildcardType { public static void main(String[] args){ //獲取TestWildcardType類的所有方法(本例中即 testWildcardType 方法) Method[] methods = TestWildcardType.class.getDeclaredMethods(); for (Method method: methods){ //獲取方法的所有引數型別 Type[] types = method.getGenericParameterTypes(); for (Type paramsType: types){ System.out.println("type: " + paramsType.toString()); //如果不是引數化型別則直接continue,執行下一個迴圈條件 if (!(paramsType instanceof ParameterizedType)){ continue; } //將當前型別強轉為引數化型別並獲取其實際引數型別(即含有萬用字元的泛型型別) Type type = ((ParameterizedType) paramsType).getActualTypeArguments()[0]; //輸出其是否為萬用字元型別 System.out.println("type instanceof WildcardType : " + ( type instanceof WildcardType)); if (type instanceof WildcardType){ int lowIndex = ((WildcardType) type).getLowerBounds().length - 1; int upperIndex = ((WildcardType) type).getUpperBounds().length - 1; //輸出上邊界與下邊界 System.out.println("getLowerBounds(): " + (lowIndex >= 0 ? ((WildcardType) type).getLowerBounds()[lowIndex] : "String ") + "; getUpperBounds(): " + (upperIndex >=0 ? ((WildcardType) type).getUpperBounds()[upperIndex]:"Object")); } } } } public void testWildcardType(List<? extends OutputStream> numberList, List<? super InputStream> upperList, List<Integer> list, InputStream inputStream){} } 複製程式碼
輸出:
-
泛型的擦除的原因以及Java中Type的作用
其實在jdk1.5之前Java中只有原始型別而沒有泛型型別,而在JDK 1.5 之後引入泛型,但是這種泛型僅僅存在於編譯階段,當在JVM執行的過程中,與泛型相關的資訊將會被擦除,如List與List都將會在執行時被擦除成為List這個型別。而型別擦除機制存在的原因正是因為如果在執行時存在泛型,那麼將要修改JVM指令集,這是非常致命的。
此外,原始型別在會生成位元組碼檔案物件,而泛型型別相關的型別並不會生成與其相對應的位元組碼檔案(因為泛型型別將會被擦除),因此,無法將泛型相關的新型別與class相統一。因此,為了程式的擴充套件性以及為了開發需要去反射操作這些型別,就引入了Type這個型別,並且新增了ParameterizedType, TypeVariable, GenericArrayType, WildcardType四個表示泛型相關的型別,再加上Class,這樣就可以用Type型別的引數來接受以上五種子類的實參或者返回值型別就是Type型別的引數。統一了與泛型有關的型別和原始型別Class。而且這樣一來,我們也可以通過反射獲取泛型型別引數。