久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

擦除實(shí)現(xiàn)的java泛型

 且看且珍惜 2014-12-05

Java中的泛型不是語言內(nèi)在的機(jī)制,而是后來添加的特性,,這樣就帶來一個(gè)問題:非泛型代碼和泛型代碼的兼容性,。泛型是JDK1.5才添加到Java中的,那么之前的代碼全部都是非泛型的,,它們?nèi)绾芜\(yùn)行在JDK1.5及以后的VM上,?為了實(shí)現(xiàn)這種兼容性,Java泛型被局限在一個(gè)很狹窄的地方,,同時(shí)也讓它變得難以理解,,甚至可以說是Java語言中最難理解的語法。

擦除

為了實(shí)現(xiàn)與非泛型代碼的兼容,,Java語言的泛型采用擦除(Erasure)來實(shí)現(xiàn),,也就是泛型基本上由編譯器來實(shí)現(xiàn),由編譯器執(zhí)行類型檢查和類型推斷,,然后在生成字節(jié)碼之前將其清除掉,,虛擬機(jī)是不知道泛型存在的。這樣的話,,泛型和非泛型的代碼就可以混合運(yùn)行,,當(dāng)然了,也顯得相當(dāng)混亂,。

在使用泛型時(shí),,會有一個(gè)對應(yīng)的類型叫做原生類型(raw type),泛型類型會被擦除到原生類型,,如Generic<T>會被查處到Generic,,List<String>會被查處到List,由于擦除,,在虛擬機(jī)中無法獲得任何類型信息,,虛擬機(jī)只知道原生類型,。下面的代碼將展示Java泛型的真相-擦除

  1. class Erasure<T> {  
  2.     private T t;  
  3.       
  4.     public void set(T t) {  
  5.         this.t = t;  
  6.     }  
  7.       
  8.     public T get() {  
  9.         return t;  
  10.     }  
  11.       
  12.     public static void main(String[] args) {      
  13.         Erasure<String> eras = new Erasure<String>();  
  14.         eras.set("not real class type");  
  15.         String value = eras.get();  
  16.           
  17.     }  
  18. }  
使用javap反編譯class文件,得到如下代碼:
  1. class com.think.generics.Erasure<T> {  
  2.   com.think.generics.Erasure();  
  3.     Code:  
  4.        0: aload_0         
  5.        1: invokespecial #12                 // Method java/lang/Object."<init>":()V  
  6.        4return          
  7.   
  8.   public void set(T);  
  9.     Code:  
  10.        0: aload_0         
  11.        1: aload_1         
  12.        2: putfield      #23                 // Field t:Ljava/lang/Object;  
  13.        5return          
  14.   
  15.   public T get();  
  16.     Code:  
  17.        0: aload_0         
  18.        1: getfield      #23                 // Field t:Ljava/lang/Object;  
  19.        4: areturn         
  20.   
  21.   public static void main(java.lang.String[]);  
  22.     Code:  
  23.        0new           #1                  // class com/think/generics/Erasure  
  24.        3: dup             
  25.        4: invokespecial #30                 // Method "<init>":()V  
  26.        7: astore_1        
  27.        8: aload_1         
  28.        9: ldc           #31                 // String not real class type  
  29.       11: invokevirtual #33                 // Method set:(Ljava/lang/Object;)V  
  30.       14: aload_1         
  31.       15: invokevirtual #35                 // Method get:()Ljava/lang/Object;  
  32.       18: checkcast     #37                 // class java/lang/String  
  33.       21: astore_2        
  34.       22return          
  35. }  
從反編譯出來的字節(jié)碼可以看到,,泛型Erasure<T>被擦除到了Erasure,,其內(nèi)部的字段T被擦除到了Object,可以看到get和set方法中都是把t作為Object來使用的,。最值得關(guān)注的是,,反編譯代碼的倒數(shù)第三行,對應(yīng)到Java代碼就是String value = eras.get();編譯器執(zhí)行了類型轉(zhuǎn)換,。這就是Java泛型的本質(zhì):對傳遞進(jìn)來的值進(jìn)行額外的編譯期檢查,,并插入對傳遞出去的值的轉(zhuǎn)型。這樣的泛型真的是泛型嗎,?

即便我們可以說,,Java中的泛型確實(shí)不是真正的泛型,但是它帶來的好處還是顯而易見的,,它使得Java的類型安全前進(jìn)了一大步,,原本需要程序員顯式控制的類型轉(zhuǎn)換,現(xiàn)在改由編譯器來實(shí)現(xiàn),,只要你按照泛型的規(guī)范去編寫代碼,,總會得到安全的保障。在這里,,我們不得不思考一個(gè)問題,,理解Java泛型,那么其核心目的是什么,?我個(gè)人認(rèn)為,,Java泛型的核心目的在于安全性,尤其是在理解泛型通配符時(shí),,一切奇怪的規(guī)則,,歸根結(jié)底都是處于安全的目的。

類型信息的丟失

由于擦除的原因,,在泛型代碼內(nèi)部,無法獲得任何有關(guān)泛型參數(shù)類型的信息,。在運(yùn)行時(shí),,虛擬機(jī)無法獲得確切的類型信息,一切以來確切類型信息的工作都無法完成,,比如instanceof操作,,和new表達(dá)式,

  1. class  Erasure<T>  {  
  2.     public void f() {  
  3.         if(arg instanceof T) //Error  
  4.         T ins = new T();//Error  
  5.         T[] array = new T[10];//error  
  6.     }  
  7. }  
那么在需要具體的類型信息時(shí),,我們就要記住Class對象來實(shí)現(xiàn)了,,凡是在運(yùn)行時(shí)需要類型信息的地方,,都使用Class對象來進(jìn)行操作,比如:
  1. class Erasure<T> {  
  2.     private Class<T> clazz;  
  3.     Erasure(Class<T> kind) {  
  4.         clazz = kind;  
  5.     }  
  6.     public void f() {  
  7.         if(clazz.isInstance(arg)) {}  
  8.         T t = clazz.newInstance();//必須要有無參構(gòu)造方法  
  9.     }  
  10. }  

泛型類中的數(shù)組

數(shù)組是Java語言中的內(nèi)建特性,,將泛型與數(shù)組結(jié)合就會有一些難以理解的問題,。首先Java中的數(shù)組是協(xié)變的,Integer是Number的子類,,所以Integer[]也是Number[]的子類,,凡是使用Number[]的地方,都可以使用Integer[]來代替,,而泛型是不協(xié)變的,,比如List<String>不是List<Object>的子類,在通配符中,,會詳細(xì)討論這些情況,。

由于無法獲得確切的類型信息,我們怎么樣創(chuàng)建泛型數(shù)組呢,?在Java中,,所有類的父類都是Object,所以可以創(chuàng)造Object類型的數(shù)組來代替泛型數(shù)組:

  1. public class Array<T> {  
  2.     private int size = 0;  
  3.     private Object[] array;  
  4.       
  5.     public Array(int size) {  
  6.         this.size = size;  
  7.         array = new Object[size];  
  8.     }  
  9.     //編譯器會保證插入進(jìn)來的是正確類型  
  10.     public void put(int index,T item) {  
  11.         array[index] = item;  
  12.     }  
  13.       
  14.     //顯式的類型轉(zhuǎn)換  
  15.     public T get(int index) {  
  16.         return (T)array[index];  
  17.     }  
  18.       
  19.     public T[] rep() {  
  20.         return (T[])array;  
  21.     }  
  22.       
  23.     private static class Father {}  
  24.     private static class Son extends Father {}  
  25.       
  26.     public static void main(String[] args) {  
  27.         Array<String> instance = new Array<String>(10);  
  28.         String[] array = instance.rep();//異常  
  29.           
  30.     }  
  31. }  
在上面的代碼中,,get()和put()都可以正確的運(yùn)行,,編譯器會保證類型的正確性。但是當(dāng)rep()返回時(shí)賦給String[]類型的數(shù)組,,則會拋出ClassCastException異常,,拋出這樣的異常是在意料之中的。在Java中,,數(shù)組其實(shí)是一個(gè)對象,,每一個(gè)類型的數(shù)組都后一個(gè)對應(yīng)的類,這個(gè)類是虛擬機(jī)生成,,比如上面的代碼中,,我們定義了Object數(shù)組,在運(yùn)行時(shí)會生成一個(gè)名為"[Ljava.lang.Object"的類,,它代表Object的一維數(shù)組,;同樣的,定義String[]數(shù)組,,其對應(yīng)的類是"[Ljava.lang.String",。從類名就可以看出,這些代表數(shù)組的類都不是合法的Java類名,,而是由虛擬機(jī)生成,,虛擬機(jī)在生成類是根據(jù)的是實(shí)際構(gòu)造的數(shù)組類型,你構(gòu)造的是Object類型的數(shù)組,,它生成的就是代表Object類型的數(shù)組的類,,無論你把它轉(zhuǎn)型成什么類型,。換句話說,沒有任何方式可以推翻底層數(shù)組的類型,。前面說到,,數(shù)組是協(xié)變的,也就是說[Ljava.lang.Object其實(shí)是[Ljava.lang.String的父類,,比如下面的代碼會得到true:
  1. String[] array = new String[10];  
  2. System.out.println(array instanceof Object[]);  
所以在將rep()返回值賦給String[]類型時(shí),,它確實(shí)是發(fā)生了類型轉(zhuǎn)換,只不過這個(gè)類型轉(zhuǎn)換不是數(shù)組元素的轉(zhuǎn)換,,并不是把Object類型的元素轉(zhuǎn)換成String,,而是把[Ljava.lang.Object轉(zhuǎn)換成了Ljava.lang.String,是父類對象轉(zhuǎn)換成子類,,必然要拋出異常,。那么問題就出來了,我們使用泛型就是為了獲得更加通用的類型,,既然我聲明的是Array<String>,,往里存儲的元素是String,得到的元素也是String,,我理所應(yīng)當(dāng)?shù)恼J(rèn)為,,我獲得的數(shù)組應(yīng)該也是String[],如果我這么做,,你卻給我拋異常,,這是幾個(gè)意思啊,!

導(dǎo)致這個(gè)問題的罪魁還是擦除,,由于擦除,沒有辦法這樣這樣定義數(shù)組:T[] array = new T[size],;為了產(chǎn)生具體類型的數(shù)組,,只能借助于Class對象,在Java類庫提供的Array類提供了一個(gè)創(chuàng)造數(shù)組的方法,,它需要數(shù)組元素類型的Class對象和數(shù)組的長度:

  1. private Class<T> kind;  
  2.     public ArrayMaker(Class<T> kind ) {  
  3.         this.kind = kind;  
  4.     }  
  5.       
  6.     @SuppressWarnings("unchecked")  
  7.     T[] create(int size) {  
  8.         T[] array = (T[])Array.newInstance(kind, size);  
  9.         System.out.println(array.getClass().getName());  
  10.         return array;  
  11.     }  
這樣構(gòu)造的就是具體類型的數(shù)組,,比如傳遞進(jìn)來的是String.class,那么調(diào)用create方法會打?。篬Ljava.lang.String,,在底層構(gòu)造的確實(shí)是String類型的數(shù)組。使用這樣的方式創(chuàng)建數(shù)組,,應(yīng)該是一種更優(yōu)雅,更安全的方式,。
以上內(nèi)容介紹了Java泛型的實(shí)質(zhì),,它的泛型更像是一顆語法糖,,一顆由編譯器包括的語法糖。由編譯器實(shí)現(xiàn)的泛型又有諸多奇怪的限制,,可泛型的功能又是如此強(qiáng)大,,使用的又是如此頻繁,所以對泛型的抱怨一直在持續(xù),,同時(shí),,泛型又是個(gè)繞不過去的彎。

    本站是提供個(gè)人知識管理的網(wǎng)絡(luò)存儲空間,,所有內(nèi)容均由用戶發(fā)布,,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式,、誘導(dǎo)購買等信息,,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,,請點(diǎn)擊一鍵舉報(bào),。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多