Java中的Type型別詳解

fb0122發表於2018-04-24

圖片來自必應

在Java中,泛型與反射是兩個重要的概念,我們幾乎能夠經常的使用到它們。而談起Type,如果有人還比較陌生的話 ,那麼說起一個它的直接實現類——Class的話,大家都應該明白了。Type是Java語言中所有型別的公共父介面。而這篇文章,主要是講述了Type的其它四個子類——ParameterizedType、 TypeVariable、GenericArrayType、WildcardType。對想了解這幾個類的朋友一個參考

首先通過一張圖片來看一下本文要介紹的Type以及其四個子類的關係與核心方法

  • 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也有普通型別變數,我們來看一下上述程式碼的輸出:

    Java中的Type型別詳解

  • 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());
          		}
            }
          }
    複製程式碼

    再來看下輸出:

    Java中的Type型別詳解

  • 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()));
                  	        	}
          			  }
            	}
          	}
    複製程式碼

    接下來看下輸出:

    Java中的Type型別詳解

  • 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型別詳解

  • 泛型的擦除的原因以及Java中Type的作用

    其實在jdk1.5之前Java中只有原始型別而沒有泛型型別,而在JDK 1.5 之後引入泛型,但是這種泛型僅僅存在於編譯階段,當在JVM執行的過程中,與泛型相關的資訊將會被擦除,如List與List都將會在執行時被擦除成為List這個型別。而型別擦除機制存在的原因正是因為如果在執行時存在泛型,那麼將要修改JVM指令集,這是非常致命的。

    此外,原始型別在會生成位元組碼檔案物件,而泛型型別相關的型別並不會生成與其相對應的位元組碼檔案(因為泛型型別將會被擦除),因此,無法將泛型相關的新型別與class相統一。因此,為了程式的擴充套件性以及為了開發需要去反射操作這些型別,就引入了Type這個型別,並且新增了ParameterizedType, TypeVariable, GenericArrayType, WildcardType四個表示泛型相關的型別,再加上Class,這樣就可以用Type型別的引數來接受以上五種子類的實參或者返回值型別就是Type型別的引數。統一了與泛型有關的型別和原始型別Class。而且這樣一來,我們也可以通過反射獲取泛型型別引數。

相關文章