<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          深入解析JAVA泛型

          共 14141字,需瀏覽 29分鐘

           ·

          2021-05-04 23:20

          1 JAVA的Type類型體系

          • 先了解下java的Type類型體系(類的類=>類型),Type是所有類型(原生類型-Class、參數(shù)化類型-Parameterizedtype、數(shù)組類型-GenericArrayType、類型變量-TypeVariable、基本類型-Class)的共同接口;前兩篇反射和注解講到的Class<T>就是Type的一實現(xiàn)類
          • Type下面又有四個子接口類ParameterizedType、TypeVariable、GenericArrayType、WildcardType
            • List<E>表示泛型,E是TypeVariable類型,List<String>則是ParameterizedType(參數(shù)化類型),List<String>里的String稱為實際參數(shù)類型
            • 具體化泛型中的類型時,可以使用 ? extends 或 ? super來表示繼承關系;如List<? extends Data>,而里面的 ? 稱為通配符類型WildcardType
            • GenericArrayType 表示一種元素類型是ParameterizedType(參數(shù)化類型)或者TypeVariable(類型變量)的數(shù)組類型,如T[] 或者 List<E>[]
          • 注解是JDK1.5才出現(xiàn)了的,為了表示被注解的類型的,加入AnnotatedElement類型,字面意思就是被注解的元素。JDK1.8又有了AnnotatedType將Type和被注解元素的概念關聯(lián)起來。
          • AnnotatedType也有四個子接口,和Type的四個子接口一一對應,如:ParameterizedType類型被注解則被編譯器解析成AnnotatedParameterizedType: @AnTest("list")List<String>list

          2 泛型的概念

          • Java 泛型(generics)是JDK1.5中引入的一個新特性,其本質(zhì)是參數(shù)化類型,解決不確定具體對象類型的問題;其所操作的數(shù)據(jù)類型被指定為一個參數(shù)(type parameter)這種參數(shù)類型可以用在類、接口和方法的創(chuàng)建中,分別稱為泛型類、泛型接口、泛型方法

          泛型: 把類型明確的工作推遲到創(chuàng)建對象或調(diào)用方法的時候才去明確的特殊的類型

          3 泛型類和泛型方法的示例

          • 泛型類的定義
          public class MainTest<T{
              private  T param;
          }
          public static void main(String[] args){
                  MainTest<String> data = new MainTest<String>(){};
                  ParameterizedType genType1 = (ParameterizedType)data.getClass().getGenericSuperclass();
            }
          • 泛型方法的定義
          public class MainTest{
              public static void main(String[] args){
                  printData("siting");
              }
              static  <T> printData(T t){
                  System.out.println(t);
                  return t;
              }
          }
          • 接口和抽象類都可以使用泛型

          4 類型擦除

          • 創(chuàng)建泛型的實例時,jvm是會把具體類型擦除的;編譯生成的字節(jié)碼中不包含泛型中的類型參數(shù),即ArrayList<String>和ArrayList<Integer>都擦除成了ArrayList,也就是被擦除成"原生類型",這就是泛型擦除
          public class MainTest {
              public static void main(String[] args){
                  List<String> strArr  = new ArrayList<>();
                  List<Integer> intArr  = new ArrayList<>();
                  Type strClazz = strArr.getClass();
                  Type intClazz = intArr.getClass();
              }
          }
          • 查看編譯后的字節(jié)碼文件是如何表示的: idea菜單 -> view -> show ByteCode
          public class MainTest<T{
              T param;
              public static void main(String[] args){
                  MainTest<String> test = new MainTest<>();
                  test.setParam("siting");
              }
              public T getParam() {  return param;   }
              public void setParam(T param) {  this.param = param;  }
          }
          public class com/MainTest {
            ...省略
            public static main([Ljava/lang/String;)V
             L0
              LINENUMBER 7 L0
              NEW com/MainTest
              DUP
              INVOKESPECIAL com/MainTest.<init> ()V
              ASTORE 1
             L1
              LINENUMBER 8 L1
              ALOAD 1
              LDC "siting"     // 調(diào)用類型擦除后的setParam(Object)
              INVOKEVIRTUAL com/MainTest.setParam (Ljava/lang/Object;)V
             L2
             ...省略//getParam 的返回值是Object
            public getParam()Ljava/lang/Object
          ;
             L0
              LINENUMBER 10 L0
              ALOAD 0
              GETFIELD com/MainTest.param : Ljava/lang/Object;
              ARETURN
             ...省略//setParam 的入?yún)⑹荗bject
            public setParam(Ljava/lang/Object;)V
             L0
              LINENUMBER 11 L0
              ALOAD 0
              ALOAD 1
              PUTFIELD com/MainTest.param : Ljava/lang/Object
          ;
              RETURN
             ...
          }
          • 可以看出T(String)都被轉(zhuǎn)換為Object類型,最初的初始化的String不見了

          5 泛型的繼承

          • 子類可以指定父類的泛型參數(shù),可以是已知類(Integer、String等),也可以用子類自己的泛型參數(shù)指定
          • 泛型被繼承時,且指定父類泛型參數(shù),則額外生成的ParameterizedType類型作為子類的父類;如果沒有指定父類泛型參數(shù),則直接繼承原生類型
          public class MainTest<T{
              T param;
              static public class SubTest1 extends MainTest<String>{}
              static public class SubTest2<Rextends MainTest<R>{}
              //SubTest3繼承的時原生類型
              static public class SubTest3 extends MainTest{}
          }

          6 泛型變量TypeVariable

          • (先臨時定義一個名稱,Test<E>里的E為泛型參數(shù));泛型變量TypeVariable:泛型的泛型參數(shù)就是TypeVariable;當父類使用子類的泛型參數(shù)指定自身的泛型參數(shù)時;或者泛型屬性定義在泛型類A<T>中,并使用泛型類A<T>的泛型參數(shù)T時,其泛型參數(shù)都會被編譯器定為泛型變量TypeVariable,而不是被擦除
          public class MainTest<T{
              List<T> param;
              public static void main(String[] args) throws Exception{
                  Class clazz =  MainTest.class;
                  TypeVariable[] typeVariable = clazz.getTypeParameters();
                  // 1
                  Field field = clazz.getDeclaredField("param");
                  ParameterizedType arrayType = (ParameterizedType)field.getGenericType();
                  // interface List<E> 的泛型類型E被T,具體化,因此其被識別為 TypeVariable
                  TypeVariable variable1 = (TypeVariable)arrayType.getActualTypeArguments()[0];
                  // 2
                  ParameterizedType type = (ParameterizedType)SubTest.class.getGenericSuperclass();
                  TypeVariable variable2 = (TypeVariable)type.getActualTypeArguments()[0];
              }
              static class SubTest<Rextends MainTest<R>{}
          }

          7 參數(shù)化類型ParameterizedType

          public interface ParameterizedType extends Type {
              //獲取實際參數(shù),List<String>里的String; 如果是List<T>則是TypeVariable類型
              Type[] getActualTypeArguments(); 
              // 獲取原始類型List<String> -> List<E>
              Type getRawType();  
              Type getOwnerType();
          }
          • 需要注意的點,我們不能直接獲取指定具體參數(shù)的泛型的類型,如Class clazz = List<String>.class編譯時不通過的;還有就是直接通過泛型類new創(chuàng)建的對象,其Class并非ParameterizedType類型,而是泛型本身的class,示例如下
          public class MainTest<T{
              public static void main(String[] args){
                  MainTest<String> str = new MainTest<String>();
                  Class variable = str.getClass();
                  Type genType1 = variable.getGenericSuperclass();
              }
          }
          • 被具體參數(shù)化的泛型才能被編譯器識別為ParameterizedType類型,有三種方式獲取ParameterizedType類型
          // 1 子類繼承泛型時,指定具體參數(shù)(可以是String等已知類型,也可以是子類的泛型參數(shù))
          // 2 獲取在類內(nèi)部定義的泛型屬性,需指定具體泛型參數(shù)
          // 3 局部代碼,可以通過泛型的匿名內(nèi)部子類(需指定具體泛型參數(shù))獲取ParameterizedType類型
          public class MainTest<T{
              List<T> list;
              public static void main(String[] args) throws NoSuchFieldException {
                  SubTest<String> str = new SubTest<>();
                  // 方式一
                  Class variable = str.getClass();
                  // 父類是(521)ParameterizedType類型
                  ParameterizedType genType = (ParameterizedType)variable.getGenericSuperclass();
                  // (521)ParameterizedType類型的原生類型是(479)class com.MainTest
                  Type clazz = genType.getRawType();
                  //MainTest.class 的原生類型是 (479)class com.MainTest
                  Class rawClazz = MainTest.class;

                  //方式二,泛型屬性
                  Field field = rawClazz.getDeclaredField("list");
                  //屬性list 類型是(546)ParameterizedType類型List<T>
                  ParameterizedType fieldType = (ParameterizedType)field.getGenericType();

                  // 方式三
                  MainTest<String> sub3 = new MainTest<String>(){};
                  // clazz3是匿名子類
                  Class clazz3 =  sub3.getClass();
                  //父類是(555)ParameterizedType類型
                  ParameterizedType genType3 = (ParameterizedType) clazz3.getGenericSuperclass();
                  // (555)ParameterizedType類型的原生類型是(479)class com.MainTest
                  Type type3 = genType3.getRawType();
              }
              public static class SubTest<Rextends MainTest<R>{ }
          }

          8 通配符(WildcardType)

          無邊界通配符:無界通配符 ? 可以適配任何引用類型:

          • 當方法參數(shù)需要傳入一個泛型時,而且無法確定其類型時。直接使用無具體泛型變量的泛型,容易造成安全隱患;若在方法代碼里進行類型轉(zhuǎn)換,極容易出現(xiàn)ClassCastException錯誤
          • 那泛型變量用Object代替不就行了?但是泛型類+具體參數(shù)轉(zhuǎn)變的ParameterizedType(參數(shù)化類型)是不存在繼承關系;即Object是String的父類,但是List<Object>和List<String>的類型是不同的兩個ParameterizedType,不存在繼承關系。于是有了類型通配符 ?
          public static void print(List list){} 
          ----->>>
          public static void print(List<?> list){} 
          • 無界通配符可以匹配任意類型;但是在使用?時,不能給泛型類的變量設置值,因為我們不知道具體類型是什么;如果強行設置新值,后面的讀容易出現(xiàn)ClassCastException錯誤。因此編譯器限制了**通配符 ?**的泛型只能讀不能寫

          上界限定通配符 < ? extends E>

          • 想接收一個List集合,它只能操作數(shù)字類型的元素【Float、Integer、Double、Byte等數(shù)字類型都行】,怎么做?可以使用List<? extends Number的子類>,表明List里的元素都是Number的子類
              public static void print(List<? extends Number> list) {
                  Number n = new Double("1.0");
                  list.add(n);
                  Number tmp = list.get(0);
              }
          • 圖片里可以看出,存在上界通配符,因為具體類型不確定,也是只能讀不能寫的

          下界限定通配符 < ? super E>

          class Parent{ }
          class Child extends Parent{ }
          public class MainTest<T> {
              T param;
              public static void main(String[] args){
                  MainTest<? super Child> parent_m = new MainTest<>();
                  parent_m.setParam(new Child());
                  Object parent = parent_m.getParam();
              }
              public T getParam() {  return param;  }
              public void setParam(T param) {  this.param = param; }
          }
          • 如果定義了通配符是誰的父類,則是下界限定通配符;此類通配符可讀可寫,轉(zhuǎn)成任意父類都不會出現(xiàn)ClassCastException錯誤。
          • 個人猜想:難道是因為通配符上界限定通配符的泛型 向下轉(zhuǎn)型容易出現(xiàn)ClassCastException錯誤,而下界限定通配符向上轉(zhuǎn)型不會出現(xiàn)ClassCastException錯誤,因此java規(guī)范限制前者編譯出錯,而后面編譯通過?

          9 泛型數(shù)組(GenericArrayType)

          public interface GenericArrayType extends Type {
              //獲得這個數(shù)組元素類型,即獲得:A<T>(A<T>[])或  T(T[])
              Type getGenericComponentType();
          }
          • GenericArrayType,泛型數(shù)組,描述的是ParameterizedType類型以及TypeVariable類型數(shù)組,即形如:Test<T>[][]、T[]等,是GenericArrayType的子接口
          public class MainTest<T{
              T[] param;
              public static void main(String[] args) throws Exception{
                  Class clazz =  MainTest.class;
                  Field field = clazz.getDeclaredField("param");
                  GenericArrayType arrayType = (GenericArrayType)field.getGenericType();
                  TypeVariable variable = (TypeVariable) arrayType.getGenericComponentType();
              }
          }
          瀏覽 90
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  黄级网站特一免费观看 | 三级亚洲| 国产激情视频区 | 免费看黄网页 | 日逼无码视频 |