成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Java隨筆-Java泛型的一點(diǎn)學(xué)習(xí)

codeGoogle / 1580人閱讀

摘要:以上代碼編譯通過,運(yùn)行通過引入泛型的同時(shí),也為了兼容之前的類庫(kù),開始引入的其實(shí)是偽泛型,在生成的字節(jié)碼中是不包含泛型中的類型信息的。進(jìn)行類型擦除后,類型參數(shù)原始類型就是擦除去了泛型信息,最后在字節(jié)碼中的類型變量的真正類型。

Java泛型

Java泛型(generics)是JDK 5中引入的一個(gè)新特性,允許在定義類和接口的時(shí)候使用類型參數(shù)(type parameter)。聲明的類型參數(shù)在使用時(shí)用具體的類型來替換。泛型最主要的應(yīng)用是在JDK 5中的新集合類框架中。泛型的引入可以解決JDK5之前的集合類框架在使用過程中較為容出現(xiàn)的運(yùn)行時(shí)類型轉(zhuǎn)換異常,因?yàn)榫幾g器可以在編譯時(shí)通過類型檢查,規(guī)避掉一些潛在的風(fēng)險(xiǎn)。

在JDK5之前,使用集合框架時(shí),是沒有類型信息的,統(tǒng)一使用Object,我找了一段JDK4 List接口的方法簽名

如下是JDK5開始引入泛型,List接口的改動(dòng),新的方法簽名,引入了類型參數(shù)。

boolean add(E e);

在JDK5之前,使用集合類時(shí),可以往其中添加任意元素,因?yàn)槠渲械念愋褪荗bject,在取出的階段做強(qiáng)制轉(zhuǎn)換,由此可能引發(fā)很多意向不到的運(yùn)行時(shí)強(qiáng)制轉(zhuǎn)換錯(cuò)誤,比如以下代碼。

public class Test1 {
    public static void main(String[] args) {
        List a = new ArrayList();
        a.add("123");
        a.add(1);             
        // 以上代碼可以正常通過編譯,其中同時(shí)含有了Integer類型和String類型
        for (int i = 0 ; i < a.size(); i++) {
            int result = (Integer)a.get(i);    // 在取出時(shí)需要對(duì)Object進(jìn)行強(qiáng)制轉(zhuǎn)型
            System.out.println(result);
        }
    }
}

如上代碼就會(huì)在運(yùn)行時(shí)階段帶來強(qiáng)轉(zhuǎn)異常,在編譯時(shí)間不能夠排查出潛在風(fēng)險(xiǎn)。

如果使用泛型機(jī)制,可以在編譯期間就檢查出List的類型插入的有問題,進(jìn)行規(guī)避,如下代碼。

public class Test1 {
    public static void main(String[] args) {
        List a = new ArrayList();
        a.add("123");          // 編譯不通過
        a.add(1);
    }
}

引入泛型后,編譯器會(huì)在編譯時(shí)先根據(jù)類型參數(shù)進(jìn)行類型檢查,杜絕掉一些潛在風(fēng)險(xiǎn)。
為何說是在編譯時(shí)檢查,因?yàn)樵谶\(yùn)行時(shí)仍然是可以通過反射,將不符合類型參數(shù)的數(shù)據(jù)插入至list中,如下代碼所示。

public class Test1 {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        List a = new ArrayList();
        List b = new ArrayList();
        a.getClass().getMethod("add",Object.class).invoke(a,"abc");    
        // 以上代碼編譯通過,運(yùn)行通過
    }
}

引入泛型的同時(shí),也為了兼容JDK5之前的類庫(kù),JDK5開始引入的其實(shí)是偽泛型,在生成的Java字節(jié)碼中是不包含泛型中的類型信息的。使用泛型的時(shí)候加上的類型參數(shù),會(huì)在編譯器在編譯的時(shí)候去掉。這個(gè)過程就稱為類型擦除。如在代碼中定義的List等類型,在編譯后都會(huì)變成List,也就自然兼容了JDK5之前的代碼。
Java的泛型機(jī)制和C++等的泛型機(jī)制實(shí)現(xiàn)不同,Java的泛型靠的還是類型擦除,目標(biāo)代碼只會(huì)生成一份,犧牲的是運(yùn)行速度。C++的模板會(huì)對(duì)針對(duì)不同的模板參數(shù)靜態(tài)實(shí)例化,目標(biāo)代碼體積會(huì)稍大一些,運(yùn)行速度會(huì)快很多。

進(jìn)行類型擦除后,類型參數(shù)原始類型(raw type)就是擦除去了泛型信息,最后在字節(jié)碼中的類型變量的真正類型。無論何時(shí)定義一個(gè)泛型類型,相應(yīng)的原始類型都會(huì)被自動(dòng)地提供。類型變量被擦除,并使用其限定類型(無限定的變量用Object)替換。

class Pair {
    private T value;
    public T getValue() {
        return value;
    }
    public void setValue(T  value) {
        this.value = value;
    }
} 
  
Pair的原始類型為:
class Pair {
    private Object value;
    public Object getValue() {
        return value;
    }
    public void setValue(Object  value) {
        this.value = value;
    }
}

在Pair中,類型擦除,使用Object,其結(jié)果就是一個(gè)普通的類,如同泛型加入java編程語(yǔ)言之前已經(jīng)實(shí)現(xiàn)的那樣。在程序中可以包含不同類型的Pair,如Pair或Pair,但是,擦除類型后它們就成為原始的Pair類型了,原始類型都是Object。ArrayList被擦除類型后,原始類型也變成了Object,通過反射我們就可以存儲(chǔ)字符串了。

在調(diào)用泛型方法的時(shí)候,可以指定泛型,也可以不指定泛型。在不指定泛型的情況下,泛型變量的類型為 該方法中的幾種類型的同一個(gè)父類的最小級(jí),直到Object。在指定泛型的時(shí)候,該方法中的幾種類型必須是該泛型實(shí)例類型或者其子類。

public class Test1 {
 
   public static void main(String[] args) {
 
      /** 不指定泛型的時(shí)候 */
      int i = Test1.add(1, 2); // 這兩個(gè)參數(shù)都是Integer,所以T為Integer類型
      Number f = Test1.add(1, 1.2);// 這兩個(gè)參數(shù)一個(gè)是Integer,以風(fēng)格是Float,所以取同一父類的最小級(jí),為Number
      Object o = Test1.add(1, "asd");// 這兩個(gè)參數(shù)一個(gè)是Integer,以風(fēng)格是Float,所以取同一父類的最小級(jí),為Object
 
      /** 指定泛型的時(shí)候 */
      int a = Test1. add(1, 2);// 指定了Integer,所以只能為Integer類型或者其子類
      int b = Test1. add(1, 2.2);// 編譯錯(cuò)誤,指定了Integer,不能為Float
      Number c = Test1. add(1, 2.2); // 指定為Number,所以可以為Integer和Float
   }
 
   // 這是一個(gè)簡(jiǎn)單的泛型方法
   public static  T add(T x, T y) {
      return y;
   }
}

因?yàn)轭愋筒脸膯栴},所有的泛型類型變量最后都會(huì)被替換為原始類型,但在泛型的使用中,我們不需要對(duì)取出的數(shù)據(jù)做強(qiáng)制轉(zhuǎn)換。

public class Test1 {
 
    public static void main(String[] args) {
        List a = new ArrayList();
        a.add(1);
 
        for (int i = 0 ; i < a.size(); i++) {
            int result = a.get(i);
            System.out.println(result);
        }
    }
}

我們從字節(jié)碼的角度來探索一下。

public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class java/util/ArrayList
       3: dup
       4: invokespecial #3                  // Method java/util/ArrayList."":()V
       7: astore_1
       8: aload_1
       9: iconst_1
      10: invokestatic  #4                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      13: invokeinterface #5,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
      18: pop
      19: iconst_0
      20: istore_2
      21: iload_2
      22: aload_1
      23: invokeinterface #6,  1            // InterfaceMethod java/util/List.size:()I
      28: if_icmpge     58
      31: aload_1
      32: iload_2
      33: invokeinterface #7,  2            // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
      38: checkcast     #8                  // class java/lang/Integer       這里JVM做了強(qiáng)轉(zhuǎn)
      41: invokevirtual #9                  // Method java/lang/Integer.intValue:()I
      44: istore_3
      45: getstatic     #10                 // Field java/lang/System.out:Ljava/io/PrintStream;
      48: iload_3
      49: invokevirtual #11                 // Method java/io/PrintStream.println:(I)V
      52: iinc          2, 1
      55: goto          21
      58: return

在偏移量38的位置可以看到,JVM使用了checkcast指令,說明雖然在編譯時(shí)進(jìn)行了類型擦除,但是JVM中仍然保留了類型參數(shù)的元信息,在取出時(shí)自動(dòng)進(jìn)行了強(qiáng)轉(zhuǎn),這也算是使用泛型的方便之處吧。

在別人的例子有看到說類型擦除和多態(tài)的沖突,舉了一個(gè)例子。

public class Test1 {
 
   public static void main(String[] args) {
      DateInter dateInter = new DateInter();
      dateInter.setValue(new Date());
      dateInter.setValue(new Object());// 編譯錯(cuò)誤
   }
}
 
class Pair {
   private T value;
 
   public T getValue() {
      return value;
   }
 
   public void setValue(T value) {
      this.value = value;
   }
}
 
class DateInter extends Pair {
   @Override
   public Date getValue() {
      return super.getValue();
   }
 
   @Override
   public void setValue(Date value) {
      super.setValue(value);
   }
}

因?yàn)樵陬愋筒脸?,父類也就變成了一個(gè)普通的類,如下所示

class Pair {
   private Object value;
 
   public Object getValue() {
      return value;
   }
 
   public void setValue(Object value) {
      this.value = value;
   }
}

但這樣setValue就從重寫變成了重載,顯然打破了想達(dá)到的目的,那么JVM是如何幫助解決這個(gè)沖突的呢?答案是 JVM幫我們搭了一個(gè)橋,具體我們從字節(jié)碼的角度再來看看。

class DateInter extends Pair {
  DateInter();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method Pair."":()V
       4: return
 
  public java.util.Date getValue();
    Code:
       0: aload_0
       1: invokespecial #2                  // Method Pair.getValue:()Ljava/lang/Object;
       4: checkcast     #3                  // class java/util/Date
       7: areturn
 
  public void setValue(java.util.Date);
    Code:
       0: aload_0
       1: aload_1
       2: invokespecial #4                  // Method Pair.setValue:(Ljava/lang/Object;)V
       5: return
 
  public void setValue(java.lang.Object);
    Code:
       0: aload_0
       1: aload_1
       2: checkcast     #3                  // class java/util/Date
       5: invokevirtual #5                  // Method setValue:(Ljava/util/Date;)V
       8: return
 
  public java.lang.Object getValue();
    Code:
       0: aload_0
       1: invokevirtual #6                  // Method getValue:()Ljava/util/Date;
       4: areturn
}

從編譯的結(jié)果來看,我們本意重寫setValue和getValue方法的子類,有4個(gè)方法,最后的兩個(gè)方法,就是編譯器自己生成的橋接方法??梢钥吹綐蚍椒ǖ膮?shù)類型都是Object,也就是說,子類中真正覆蓋父類兩個(gè)方法的就是這兩個(gè)我們看不到的橋方法,打在我們自己定義的setvalue和getValue方法上面的@Oveerride只不過是假象。而橋方法的內(nèi)部實(shí)現(xiàn),就只是去調(diào)用我們自己重寫的那兩個(gè)方法。
所以,虛擬機(jī)巧妙的使用了巧方法,來解決了類型擦除和多態(tài)的沖突。

最后附上最近在瀏覽一些別人經(jīng)驗(yàn)時(shí)得到一些tips。

使用JSON串反序列化對(duì)象集合時(shí),記得標(biāo)注對(duì)象的class類型,不然會(huì)得到一個(gè)只有原始類型也就是Object的集合,可能引起類型轉(zhuǎn)換錯(cuò)誤,尤其是在服務(wù)調(diào)用的這種場(chǎng)景下。

重視編譯器提出的警告信息。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/70683.html

相關(guān)文章

  • java的集合和型的知識(shí)點(diǎn)歸納1

    摘要:接口也是集合中的一員,但它與接口有所不同,接口與接口主要用于存儲(chǔ)元素,而主要用于迭代訪問即遍歷中的元素,因此對(duì)象也被稱為迭代器。迭代器的實(shí)現(xiàn)原理我們?cè)谥鞍咐呀?jīng)完成了遍歷集合的整個(gè)過程。 【Collection、泛型】 主要內(nèi)容 Collection集合 迭代器 增強(qiáng)for 泛型 教學(xué)目標(biāo) [ ] 能夠說出集合與數(shù)組的區(qū)別 [ ] 說出Collection集合的常用功能 [ ]...

    daryl 評(píng)論0 收藏0
  • Java泛型:類型擦除

    博客地址:Java泛型:類型擦除 前情回顧 Java泛型:泛型類、泛型接口和泛型方法 類型擦除 代碼片段一 Class c1 = new ArrayList().getClass(); Class c2 = new ArrayList().getClass(); System.out.println(c1 == c2); /* Output true */ 顯然在平時(shí)使用中,ArrayList...

    Hanks10100 評(píng)論0 收藏0
  • 聊聊Java泛型及實(shí)現(xiàn)

    摘要:靜態(tài)變量是被泛型類的所有實(shí)例所共享的。所以引用能完成泛型類型的檢查。對(duì)于這個(gè)類型系統(tǒng),有如下的一些規(guī)則相同類型參數(shù)的泛型類的關(guān)系取決于泛型類自身的繼承體系結(jié)構(gòu)。事實(shí)上,泛型類擴(kuò)展都不合法。 前言 和C++以模板來實(shí)現(xiàn)靜多態(tài)不同,Java基于運(yùn)行時(shí)支持選擇了泛型,兩者的實(shí)現(xiàn)原理大相庭徑。C++可以支持基本類型作為模板參數(shù),Java卻只能接受類作為泛型參數(shù);Java可以在泛型類的方法中取得...

    lewif 評(píng)論0 收藏0
  • java基礎(chǔ)鞏固-泛型基礎(chǔ)知識(shí)整理

    摘要:當(dāng)某個(gè)類型變量只在整個(gè)參數(shù)列表的所有參數(shù)和返回值中的一處被應(yīng)用了,那么根據(jù)調(diào)用方法時(shí)該處的實(shí)際應(yīng)用類型來確定。即直接根據(jù)調(diào)用方法時(shí)傳遞的參數(shù)類型或返回值來決定泛型參數(shù)的類型。 標(biāo)簽: java [TOC] 本文對(duì)泛型的基本知識(shí)進(jìn)行較為全面的總結(jié),并附上簡(jiǎn)短的代碼實(shí)例,加深記憶。 泛型 將集合中的元素限定為一個(gè)特定的類型。 術(shù)語(yǔ) ArrayList -- 泛型類型 ArrayLis...

    KoreyLee 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

codeGoogle

|高級(jí)講師

TA的文章

閱讀更多
最新活動(dòng)
閱讀需要支付1元查看
<