隨筆-126  評(píng)論-247  文章-5  trackbacks-0

          泛型的好處:


              泛型的主要好處就是讓編譯器保留參數(shù)的類型信息,執(zhí)行類型檢查,執(zhí)行類型轉(zhuǎn)換(casting)操作,編譯器保證了這些類型轉(zhuǎn)換(casting)的絕對(duì)無誤。


                  
          /******* 不使用泛型類型 *******/
                  List list1 
          = new ArrayList();
                  list1.add(
          8080);                                  //編譯器不檢查值
                  String str1 = (String)list1.get(0); //需手動(dòng)強(qiáng)制轉(zhuǎn)換,如轉(zhuǎn)換類型與原數(shù)據(jù)類型不一致將拋出ClassCastException異常
                  
                  
          /******* 使用泛型類型 *******/
                  List
          <String> list2 = new ArrayList<String>();
                  list2.add(
          "value");                 //[類型安全的寫入數(shù)據(jù)] 編譯器檢查該值,該值必須是String類型才能通過編譯
                  String str2 = list2.get(0); //[類型安全的讀取數(shù)據(jù)] 不需要手動(dòng)轉(zhuǎn)換
                  


          泛型的類型擦除:

              Java 中的泛型只存在于編譯期,在將 Java 源文件編譯完成 Java 字節(jié)代碼中是不包含泛型中的類型信息的。使用泛型的時(shí)候加上的類型參數(shù),會(huì)被編譯器在編譯的時(shí)候去掉。

              這個(gè)過程就稱為類型擦除(type erasure)。


                  List
          <String>    list1 = new ArrayList<String>();
                  List
          <Integer> list2 = new ArrayList<Integer>();
                  
                  System.out.println(list1.getClass() 
          == list2.getClass()); // 輸出結(jié)果: true
                  System.out.println(list1.getClass().getName()); // 輸出結(jié)果: java.util.ArrayList
                  System.out.println(list2.getClass().getName()); // 輸出結(jié)果: java.util.ArrayList
                  

          在以上代碼中定義的 List<String> 和 List<Integer> 等類型,在編譯之后都會(huì)變成 List,而由泛型附加的類型信息對(duì) JVM 來說是不可見的,所以第一條打印語句輸出 true,

          第二、第三條打印語句都輸出 java.util.ArrayList,這都說明 List<String> 和 List<Integer> 的對(duì)象使用的都是同一份字節(jié)碼,運(yùn)行期間并不存在泛型。

          來看一個(gè)簡單的例子:


          package test;

          import java.util.List;
          /**
           * -----------------------------------------
           * @描述  類型擦除
           * @作者  fancy
           * @郵箱  fancydeepin@yeah.net
           * @日期  2012-8-25 <p>
           * -----------------------------------------
           
          */
          public class GenericsApp {

              
              
          public void method(List<String> list){
                  
              }
              
              
          /*
               * 編譯出錯(cuò),這兩個(gè)方法不屬于重載,由于類型的擦除,使得這兩個(gè)方法的參數(shù)列表的參數(shù)均為List類型,
               * 這就相當(dāng)于同一個(gè)方法被聲明了兩次,編譯自然無法通過了
               * 
              public void method(List<Integer> list){
                  
              }
              
          */
              
          }

          以此類為例,在 cmd 中 編譯 GenericsApp.java 得到字節(jié)碼,然后再反編譯這份字節(jié)碼:



          從圖中可以看出,經(jīng)反編譯后的源碼中 method 方法的參數(shù)變成了 List 類型,說明泛型的類型被擦除了,字節(jié)碼文件中不存在泛型,也就是說,運(yùn)行期間泛型并不存在,它在

          編譯完成之后就已經(jīng)被擦除了。


          泛型類型的子類型:

              泛型類型跟其是否是泛型類型的子類型沒有任何關(guān)系。


                  List
          <Object> list1;
                  List
          <String> list2;
                  
                  list1 
          = list2; // 編譯出錯(cuò)
                  list2 = list1; // 編譯出錯(cuò)

          在 Java 中,Object 類是所有類的超類,自然而然的 Object 類是 String 類的超類,按理,將一個(gè) String 類型的對(duì)象賦值給一個(gè) Object 類型的對(duì)象是可行的,

          但是泛型中并不存在這樣的邏輯,泛型類型跟其是否子類型沒有任何關(guān)系。


          泛型中的通配符(?):

              由于泛型類型與其子類型存在不相關(guān)性,那么在不能確定泛型類型的時(shí)候,可以使用通配符(?),通配符(?)能匹配任意類型。


                  List
          <?> list;
                  List
          <Object> list1 = null;
                  List
          <String>  list2 = null;
                  
                  list 
          = list1;
                  list 
          = list2;


          限定通配符的上界:


                  ArrayList
          <? extends Number> collection = null;
                  
                  collection 
          = new ArrayList<Number>();
                  collection 
          = new ArrayList<Short>();
                  collection 
          = new ArrayList<Integer>();
                  collection 
          = new ArrayList<Long>();
                  collection 
          = new ArrayList<Float>();
                  collection 
          = new ArrayList<Double>();
                  

           ? extends XX,XX 類是用來限定通配符的上界,XX 類是能匹配的最頂層的類,它只能匹配 XX 類以及 XX 類的子類。在以上代碼中,Number 類的實(shí)現(xiàn)類有:

          AtomicInteger、AtomicLong、 BigDecimal、 BigInteger、 Byte、 Double、 Float、 Integer、 Long、 Short ,因此以上代碼均無錯(cuò)誤。


          限定通配符的下界:


                  ArrayList
          <? super Integer> collection = null;
                  
                  collection 
          = new ArrayList<Object>();
                  collection 
          = new ArrayList<Number>();
                  collection 
          = new ArrayList<Integer>();
                  

           ? super XX,XX 類是用來限定通配符的下界,XX 類是能匹配的最底層的類,它只能匹配 XX 類以及 XX 類的超類,在以上代碼中,Integer 類的超類有:

          Number、Object,因此以上代碼均能通過編譯無誤。


          通過反射獲得泛型的實(shí)際類型參數(shù):

              java.lang.Class 類從 Java 1.5 起(如果沒記錯(cuò)的話),提供了一個(gè) getGenericSuperclass() 方法來獲取直接超類的泛型類型


          package test;

          import java.lang.reflect.ParameterizedType;
          /**
           * -----------------------------------------
           * @描述  泛型的實(shí)際類型參數(shù)
           * @作者  fancy
           * @郵箱  fancydeepin@yeah.net
           * @日期  2012-8-25 <p>
           * -----------------------------------------
           
          */
          public class Base<T> {

              
          private Class<T> entityClass;
              
              
          //代碼塊,也可將其放置到構(gòu)造子中
              {
                  entityClass 
          =(Class<T>)((ParameterizedType)getClass().getGenericSuperclass()).getActualTypeArguments()[0];
                      
              }
              
              
          //泛型的實(shí)際類型參數(shù)的類全名
              public String getEntityName(){
                  
                  
          return entityClass.getName();
              }
              
              
          //泛型的實(shí)際類型參數(shù)的類名
              public String getEntitySimpleName(){
                  
                  
          return entityClass.getSimpleName();
              }

              
          //泛型的實(shí)際類型參數(shù)的Class
              public Class<T> getEntityClass() {
                  
          return entityClass;
              }
              
          }

          (Class<T>)((ParameterizedType)getClass().getGenericSuperclass()).getActualTypeArguments()[0];   相當(dāng)于:


              
          //代碼塊,也可將其放置到構(gòu)造子中
              {
                  
          //entityClass =(Class<T>)((ParameterizedType)getClass().getGenericSuperclass()).getActualTypeArguments()[0];
                  try {
                      Class
          <?> clazz = getClass(); //獲取實(shí)際運(yùn)行的類的 Class
                      Type type = clazz.getGenericSuperclass(); //獲取實(shí)際運(yùn)行的類的直接超類的泛型類型
                      if(type instanceof ParameterizedType){ //如果該泛型類型是參數(shù)化類型
                          Type[] parameterizedType = ((ParameterizedType)type).getActualTypeArguments();//獲取泛型類型的實(shí)際類型參數(shù)集
                          entityClass = (Class<T>) parameterizedType[0]; //取出第一個(gè)(下標(biāo)為0)參數(shù)的值
                      }
                  } 
          catch (Exception e) {
                      e.printStackTrace();
                  }
                      
              }
              

          注意,獲取 Class 實(shí)例的時(shí)候是用 getClass(),而不是用 Base.class,獲取 Class 的方式有三種,這是其中的兩種,還有一種是 Class.forName("類全名"),如需了解反射的基礎(chǔ)知識(shí)

          請(qǐng)前往上一篇隨筆 java 反射基礎(chǔ)

          那么,Base.class 與 getClass(),這兩個(gè)方法來獲取類的字節(jié)碼的時(shí)候,Base.class 是寫死了的,它得到的永遠(yuǎn)是 Base 類的字節(jié)碼,

          而 getClass() 方法則不同,在上面代碼注釋中的第一、二行注釋我用了“實(shí)際運(yùn)行的類”6個(gè)字,這幾個(gè)字很重要,是一定要理解的。

          為了方便大家的理解,下面插加一個(gè)小例子來加以說明 類.class 與 getClass() 兩種方法來獲取類的字節(jié)碼有什么區(qū)別:


          package test;
          /**
           * -----------------------------------------
           * @描述  超類
           * @作者  fancy
           * @郵箱  fancydeepin@yeah.net
           * @日期  2012-8-25 <p>
           * -----------------------------------------
           
          */
          public class Father {

              
          public Father (){
                  
                  System.out.println(
          "Father 類的構(gòu)造子:");
                  System.out.println(
          "Father.class :" + Father.class);
                  System.out.println(
          "getClass()      :" + getClass());
              }
              
          }


          package test;

          /**
           * -----------------------------------------
           * @描述  超類的子類(超類的實(shí)現(xiàn)類)
           * @作者  fancy
           * @郵箱  fancydeepin@yeah.net
           * @日期  2012-8-25 <p>
           * -----------------------------------------
           
          */
          public class Children extends Father{

              
          }


          package test;
          /**
           * -----------------------------------------
           * @描述  測試類
           * @作者  fancy
           * @郵箱  fancydeepin@yeah.net
           * @日期  2012-8-25 <p>
           * -----------------------------------------
           
          */
          public class Test {

              
          public static void main(String[] args){
                  
                  
          new Children(); //實(shí)際運(yùn)行的類是Children(Father類的子類或者說是實(shí)現(xiàn)類)
              }
              
          }

          后臺(tái)打印輸出的結(jié)果:

          Father 類的構(gòu)造子:
          Father.
          class :class test.Father
          getClass()      :
          class test.Children

           從打印出的結(jié)果看來,類.class 與 getClass() 的區(qū)別很明了了,getClass() 獲取的是實(shí)際運(yùn)行的類的字節(jié)碼,它不一定是當(dāng)前類的 Class,有可能是當(dāng)前類的子類的 Class,具體是哪

          個(gè)類的 Class,需要根據(jù)實(shí)際運(yùn)行的類來確定,new 哪個(gè)類,getClass() 獲取的就是哪個(gè)類的 Class,而 類.class 獲取得到的 Class 永遠(yuǎn)只能是該類的 Class,這點(diǎn)是有很大的區(qū)別的。

          如果 getClass() 理解了,那 clazz.getGenericSuperclass() 也應(yīng)該能夠理解了的,千萬不要以為 clazz.getGenericSuperclass() 獲取得到的是 Object 類那就成了,

          實(shí)際上假如當(dāng)前運(yùn)行的類是 Base 類的子類,那么 clazz.getGenericSuperclass() 獲取得到的就是 Base 類。

          (Class<T>) parameterizedType[0],怎么就知道第一個(gè)參數(shù)(parameterizedType[0])就是該泛型的實(shí)際類型呢?很簡單,因?yàn)?Base<T> 的泛型的類型

          參數(shù)列表中只有一個(gè)參數(shù),所以,第一個(gè)元素就是泛型 T 的實(shí)際參數(shù)類型。


          package test;
          /**
           * -----------------------------------------
           * @描述  測試類
           * @作者  fancy
           * @郵箱  fancydeepin@yeah.net
           * @日期  2012-8-25 <p>
           * -----------------------------------------
           
          */
          public class Test {

              
          public static void main(String[] args){
                  
                  Base
          <String> base = new Base<String>();
                  System.out.println(base.getEntityClass());                        
          //打印輸出 null
              
          //    System.out.println(base.getEntityName());                //拋出 NullPointerException 異常
              
          //    System.out.println(base.getEntitySimpleName()); //拋出 NullPointerException 異常
              }
              
          }

          從打印的結(jié)果來看,Base 類并不能直接來使用,為什么會(huì)這樣?原因很簡單,由于 Base 類中的 clazz.getGenericSuperclass() 方法,如果隨隨便便的就確定 Base 類的泛型的類型

          參數(shù),則很可能無法通過 Base 類中的 if 判斷,導(dǎo)致 entityClass 的值為 null,像這里的 Base<String>,String 的 超類是 Object,而 Object 并不能通過 if 的判斷語句。

          Base 類不能夠直接來使用,而是應(yīng)該通過其子類來使用,Base 應(yīng)該用來作為一個(gè)基類,我們要用的是它的具體的子類,下面來看下代碼,它的子類也不是隨便寫的:


          package test;
          /**
           * -----------------------------------------
           * @描述  Base類的實(shí)現(xiàn)類
           * @作者  fancy
           * @郵箱  fancydeepin@yeah.net
           * @日期  2012-8-25 <p>
           * -----------------------------------------
           
          */
          public class Child extends Base<Child>{

          }

          從上面代碼來看,Base 的泛型類型參數(shù)就是 Base 的子類本身,這樣一來,當(dāng)使用 Base 類的子類 Child 類時(shí),Base 類就能準(zhǔn)確的獲取到當(dāng)前實(shí)際運(yùn)行的類的 Class,來看下怎么使用


          package test;
          /**
           * -----------------------------------------
           * @描述  測試類
           * @作者  fancy
           * @郵箱  fancydeepin@yeah.net
           * @日期  2012-8-25 <p>
           * -----------------------------------------
           
          */
          public class Test {

              
          public static void main(String[] args){
                  
                  Child child 
          = new Child();
                  System.out.println(child.getEntityClass());
                  System.out.println(child.getEntityName());
                  System.out.println(child.getEntitySimpleName());
              }
              
          }

          后臺(tái)打印輸出的結(jié)果:


          class test.Child
          test.Child
          Child

          好了,文章接近尾聲了,如果你能理解透這個(gè)例子,你可以將這個(gè)思想運(yùn)用到 DAO 層面上來,以 Base 類作為所有 DAO 實(shí)現(xiàn)類的基類,在 Base 類里面實(shí)現(xiàn)數(shù)據(jù)庫的 CURD 等基本

          操作,然后再使所有具體的 DAO 類來實(shí)現(xiàn)這個(gè)基類,那么,實(shí)現(xiàn)這個(gè)基類的所有的具體的 DAO 都不必再實(shí)現(xiàn)數(shù)據(jù)庫的 CURD 等基本操作了,這無疑是一個(gè)很棒的做法。


          (通過反射獲得泛型的實(shí)際類型參數(shù))補(bǔ)充:

          泛型反射的關(guān)鍵是獲取 ParameterizedType 接口,再調(diào)用 ParameterizedType 接口中的 getActualTypeArguments() 方法就可獲得實(shí)際綁定的類型。

          由于去參數(shù)化(擦拭法),也只有在 超類(調(diào)用 getGenericSuperclass 方法) 或者成員變量(調(diào)用 getGenericType 方法)或者方法(調(diào)用 getGenericParameterTypes 方法)

          像這些有方法返回 ParameterizedType 類型的時(shí)候才能反射成功。

          上面只談到超類如何反射,下面將變量和方法的兩種反射補(bǔ)上:

          通過方法,反射獲得泛型的實(shí)際類型參數(shù):

          package test;

          import java.lang.reflect.Method;
          import java.lang.reflect.ParameterizedType;
          import java.lang.reflect.Type;
          import java.util.Collection;

          /**
           * -----------------------------------------
           * @描述  測試類
           * @作者  fancy
           * @郵箱  fancydeepin@yeah.net
           * @日期  2012-8-26 <p>
           * -----------------------------------------
           
          */
          public class Test {

              
          public static void main(String[] args){
                  
          /**
                   * 泛型編譯后會(huì)去參數(shù)化(擦拭法),因此無法直接用反射獲取泛型的參數(shù)類型
                   * 可以把泛型用做一個(gè)方法的參數(shù)類型,方法可以保留參數(shù)的相關(guān)信息,這樣就可以用反射先獲取方法的信息
                   * 然后再進(jìn)一步獲取泛型參數(shù)的相關(guān)信息,這樣就得到了泛型的實(shí)際參數(shù)類型
                   
          */
                  
          try {
                      Class
          <?> clazz = Test.class//取得 Class
                      Method method = clazz.getDeclaredMethod("applyCollection", Collection.class); //取得方法
                      Type[] type = method.getGenericParameterTypes(); //取得泛型類型參數(shù)集
                      ParameterizedType ptype = (ParameterizedType)type[0];//將其轉(zhuǎn)成參數(shù)化類型,因?yàn)樵诜椒ㄖ蟹盒褪菂?shù),且Number是第一個(gè)類型參數(shù)
                      type = ptype.getActualTypeArguments(); //取得參數(shù)的實(shí)際類型
                      System.out.println(type[0]); //取出第一個(gè)元素
                  } catch (Exception e) {
                      e.printStackTrace();
                  }
              }
              
              
          //聲明一個(gè)空的方法,并將泛型用做為方法的參數(shù)類型
              public void applyCollection(Collection<Number> collection){
                  
              }
          }

          后臺(tái)打印輸出的結(jié)果:


          class java.lang.Number


          通過字段變量,反射獲得泛型的實(shí)際類型參數(shù):

          package test;

          import java.lang.reflect.Field;
          import java.lang.reflect.Method;
          import java.lang.reflect.ParameterizedType;
          import java.lang.reflect.Type;
          import java.util.Collection;
          import java.util.Map;

          /**
           * -----------------------------------------
           * @描述  測試類
           * @作者  fancy
           * @郵箱  fancydeepin@yeah.net
           * @日期  2012-8-26 <p>
           * -----------------------------------------
           
          */
          public class Test {

              
          private Map<String, Number> collection;
              
              
          public static void main(String[] args){
                  
                  
          try {
                      
                      Class
          <?> clazz = Test.class//取得 Class
                      Field field = clazz.getDeclaredField("collection"); //取得字段變量
                      Type type = field.getGenericType(); //取得泛型的類型
                      ParameterizedType ptype = (ParameterizedType)type; //轉(zhuǎn)成參數(shù)化類型
                      System.out.println(ptype.getActualTypeArguments()[0]); //取出第一個(gè)參數(shù)的實(shí)際類型
                      System.out.println(ptype.getActualTypeArguments()[1]); //取出第二個(gè)參數(shù)的實(shí)際類型
                      
                  } 
          catch (Exception e) {
                      e.printStackTrace();
                  }
              }
              
          }

          后臺(tái)打印輸出的結(jié)果:


          class java.lang.String
          class java.lang.Number



            
          posted on 2012-08-25 15:06 fancydeepin 閱讀(14322) 評(píng)論(9)  編輯  收藏

          評(píng)論:
          # re: java 泛型 深入 2012-08-25 23:16 | wool.var
          那就精華經(jīng)過作者的分析有點(diǎn)似懂非懂  回復(fù)  更多評(píng)論
            
          # re: java 泛型 深入[未登錄] 2012-08-26 10:30 | Kane
          既然泛型信息在編譯后已完全抹去,那在后面的例子中是如何在運(yùn)行時(shí)得到參數(shù)化的類型呢  回復(fù)  更多評(píng)論
            
          # re: java 泛型 深入 2012-08-26 15:34 | fancydeepin
          回復(fù) @Kane:

          這個(gè)問題問的好!我也跟你有一樣的疑惑,當(dāng)時(shí)我立即去查 java.lang.Class 的源碼,找到 getGenericSuperclass() 的實(shí)現(xiàn):
          public Type getGenericSuperclass() {
          if (getGenericSignature() != null) {
          // Historical irregularity:
          // Generic signature marks interfaces with superclass = Object
          // but this API returns null for interfaces
          if (isInterface())
          return null;
          return getGenericInfo().getSuperclass();
          } else
          return getSuperclass();
          }
          但是以上實(shí)現(xiàn)中的 getGenericSignature 和 getSuperclass 兩個(gè)方法都是用 native 聲明的本地方法,找不到它們的源碼,沒有收獲。
          文章中的 Base 類和 Child 類在編譯完成后的字節(jié)碼文件中,你通過反編譯來看,真真切切是沒有保留泛型信息,編譯期間泛型被擦除:

          在文章的代碼中,如果想輸出查看一下type的類型:
          ……
          Type type = clazz.getGenericSuperclass();
          System.out.println(type);
          ……
          打印確實(shí)能夠輸出我們想要的:test.Base<test.Child>
          getGenericSuperclass() 具體是怎么取到泛型的信息的我也不太清楚,只是知道用這個(gè)方法能取得到,就我知道的,想通過反射取得泛型信息有三種方法,一種就是文章中提到的,一種是變量,另外一種就是通過把泛型用做一個(gè)方法的參數(shù)類型,方法可以保留參數(shù)的相關(guān)信息,這樣就可以用反射來先獲取方法的信息,然后再進(jìn)一步獲取泛型參數(shù)的相關(guān)信息,這樣也能得到泛型的實(shí)際參數(shù)類型。
            回復(fù)  更多評(píng)論
            
          # re: java 泛型 深入 2012-11-09 00:24 | 紅淚
          寫得不錯(cuò)!  回復(fù)  更多評(píng)論
            
          # re: java 泛型 深入[未登錄] 2013-04-02 14:04 | atom
          條理挺清晰的,不錯(cuò)的入門文章  回復(fù)  更多評(píng)論
            
          # re: java 泛型 深入 2013-09-08 22:56 | zhhw
          感謝寫的這么好,好久的小疑惑解決了  回復(fù)  更多評(píng)論
            
          # re: java 泛型 深入 2013-10-26 16:35 | forenroll
          樓主,我做了一個(gè)試驗(yàn),我反編譯的結(jié)果跟你的有出入。
          試驗(yàn)的情況寫在我的博客上,你移步過去看看,幫忙解答一下,多謝!  回復(fù)  更多評(píng)論
            
          # re: java 泛型 深入 2013-10-26 16:36 | forenroll
          # re: java 泛型 深入 2014-08-27 14:09 | daoyong
          泛型信息在運(yùn)行時(shí)會(huì)進(jìn)行擦除,主要是為了向前兼容,因?yàn)镴DK5 以前的版本并沒有泛型。但是泛型參數(shù)等信息在JVM中仍然可以獲取,并沒有從class文件中去除,用其它的反編譯工具是可以看得到的。  回復(fù)  更多評(píng)論
            

          只有注冊用戶登錄后才能發(fā)表評(píng)論。


          網(wǎng)站導(dǎo)航:
           
          主站蜘蛛池模板: 晋宁县| 温州市| 黄陵县| 佛教| 永靖县| 政和县| 铅山县| 济南市| 文登市| 新乡县| 繁昌县| 灵石县| 昭苏县| 绿春县| 南漳县| 郴州市| 云霄县| 航空| 元阳县| 龙游县| 罗甸县| 砀山县| 和田市| 肥城市| 犍为县| 尼勒克县| 敦化市| 吴桥县| 诸城市| 陆川县| 高雄县| 通州市| 临沭县| 唐河县| 松滋市| 孝感市| 彭泽县| 福鼎市| 勃利县| 塘沽区| 咸阳市|