溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

JAVA泛型淺析

發布時間:2020-08-10 21:40:38 來源:ITPUB博客 閱讀:216 作者:Sky-Tiger 欄目:編程語言
本文主要列舉了在使用Java泛型時應該注意的問題。Java泛型是Java5的一個重要特性,它和自動裝箱、變長參數等新特性一起,提升了Java代碼的健壯性和易用性,但SUN本身過分強調向前的兼容性,也引入了不少問題和麻煩。[@more@]

JAVA泛型和C++泛型的區別:

Java的泛型被定義成擦除,而C++的泛型則是擴展;

對于C++模板,當參數類型為不同的類型時,生成的模板實例也是不同的類型,如:定義類模板

Template class A : public B;

當實例化模板時

A a;

A b;

這里的ab是兩種不同的類型的實例;

Java則不是這樣的,如泛化List,分別用IntegerString來實例化,

List l;

List s;

通過反射機制看lsclass,他們都是List!所有的參數類型都被編譯器擦除了!

這樣造成的結果是以往用C++模板可以實現某種契約(contract )的功能在Java中變得很另類了,舉一個例子:

C++代碼:

Template class A{

Private:

T x;

Public:

Void func(){

int ret = x.foo(12);

}

}

上面這段代碼在C++中經常能夠看到,它暗中就設定了一個契約:凡是能否實例化這個模板的類型T,其必須具有一個公共的函數foo,這個函數返回整數,并且接受一個整數做為參數。

Java的代碼:

public class A{

private E e;

public void func(){

int ret = e.foo(12); //編譯錯誤啊

}

}

編譯器給出的錯誤原因是foo函數對于類型中E是未定義的??!造成這樣的問題的原因有兩個:

1、 C++的編譯器直到模板被使用(實例化)的時候才去編譯模板,如果你不去使用模板C++編譯器不會編譯模板;而實例化的時候編譯器已經能夠確定具體的參數類型,所以能夠檢測契約是否符合;Java的編譯器不是這樣工作的,所以它在編譯模板類型的時候不能夠確定E到底有沒有這個foo函數;

2、 類型擦除的結果;修改一下上面的程序,我們看看在func中到底能夠調用什么函數,一看只能調用Object對象中的函數。所有的類型E都被擦除成Object了!

如果真的要想實現類似C++的契約,就必須確保參數類型E不被擦除成Object!需要如下修改代碼:

public class A{

private E e;

public void func(){

int ret = e.foo(12);

}

}

Class B{

Public int foo(int param){…};

}

這樣雖然可以實現我們期望的形式,但是約束的程度要比C++的強很多,C++中,只要任意類型,其具有一個符合契約的函數,就可以實例化模板,而Java中,則要求所有的類型必須是給定類型的子類才可以實例化模板;

擦除的原則:

1、 所有參數化容器類都被擦除成非參數化的(raw type);如List、List>都被擦除成List;

2、 所有參數化數組都被擦除成非參數化的數組;如List[],被擦除成List[];

3、 Raw type的容器類,被擦除成其自身,如List 被擦除成List;

4、 原生類型(int,String還有wrapper類)都擦除成他們的自身;

5、 參數類型E,被擦除成Object;

6、 所有約束參數如、都被擦除成E;

7、 如果有多個約束,擦除成第一個,如,則擦除成Object;

例如:

泛化代碼:

                 List                               words.add("Hello ");
                               words.add("world!");
                               String s = words.get(0)+words.get(1);
         擦除后就變成了:
                 List words = new ArrayList();
                               words.add("Hello ");
                               words.add("world!");
                               String s = (         擦除后的代碼和以前沒有泛型時候寫的代碼沒有任何區別!
     再例如:
         泛化代碼:

public class textReader<T>{

private T a;

public textReader(T b){

this.a = b;

}

public T getA(){

return a;

}

public static void main(String[] agrvs){

String in = "1234567890";

textReader test = new textReader(in);

String out = test.getA();

System.out.println(out);

}

               }
               擦除后(所有類型參數都被去掉,T被擦除成Object)就變成(注意紅色部分)

public class textReader{

private Object a;

public textReader(Object b){

this.a = b;

}

public Object getA(){

return a;

}

public static void main(String[] agrvs){

String in = "1234567890";

textReader test = new textReader (in);

String out = (String)test.getA();

System.out.println(out);

}

               }

擦除所帶來的問題:

1、 靜態成員共享問題

        List ints = Arrays.asList(1,2,3);
               List strings = Arrays.asList("one","two");
               assert ints.getClass() == strings.getClass();

intsstrings兩個對象最終被擦除成具有相同類型的(List)的對象,于是這兩個對象共享List的靜態成員,于是就可以得出這樣的結論,所有泛化類型的靜態成員被其所有的實例化對象共享,因此也就要求所有靜態成員不能夠是泛化的!

class Foo {
  private final T value;
  private   public T getValue() { return value; }
                   public 2、  過載(overload)沖突問題

函數過載的定義這樣的:在一個類的范圍內,如果兩個函數具有相同的函數名稱,不同的參數(返回值不考慮)就互相稱為過載函數??匆粋€例子:

Class A{

Public int foo(int a){};

Public int foo(float f){}; // 是過載,編譯沒有問題

Public int foo(int a){};

Public float foo(int f){}; // 報錯

Public static int foo1(List a){}

Public static int foo1(List s){} //編譯有錯誤,因為所有的List都被擦除成List,這樣兩個函數重復定義,報錯;

Public static int foo1(List a){}

Public static String foo1(List s){} //沒有問題,編譯器不會報錯!

}

3、接口實現

一個類不能同時實現具有相同擦除效果的接口,例如:

class Foo implements Comparable, Comparable

繼承關系

1、 原始繼承:就是我們經常提到的繼承關系,如ArrayListList的子類;

2、 泛化繼承:

a) 泛化后的ArrayList依舊是List的子類;其中T是參數化類型

b) 如果類型T是類型B的子類,那么List不是List的子類

c) ListList的子類

d) ListList的子類

e) ListList的子類

f) ListList的子類

g) 如果類型T是類型B的子類,那么T[]B[]的子類

3、 關于協變式(covariant)、不變式(invariant)和反變式(contravariant)

a) 數組和擴展類(extends)的泛化是協變式,即如果類型T是類型B的子類,那么T[]B[]的子類;ListList的子類

b) 非擴展類泛型是不變式,即如果類型T是類型B的子類,那么List不是List的子類

c) Super類泛型是反變式,即如果BT的超類,則List List的子類

GetPut原則:

當從一個泛化的結構中取數據的時候請使用extends通配,當往一個泛化的結構中放數據的時候請使用super通配;

當需要同時從一個泛化結構中讀取和寫入數據是,請不使用通配符號;

為什么會這樣,我們簡單的分析一下:假定類型T繼承自AB,類型CD又從T類型繼承,那么List中存放的只能是T類型或是C或是D類型,里面存放的類型都可以向上castT,所以從List中取東西編譯器能夠正確處理,只要映射到T就可以了(T是他們的父類)!往List放東西就不一樣的,原來里面放的是T/C還是D都被擦除成T,所以編譯器不知道原來到底存放的是什么類型,無法保證類型安全,所以這個操作被禁止!

List中存放的是A/B或是T,往ListT是允許的,因為T總是可以向上轉換成A或是B,但是從里面取東西就有問題了,編譯器還是不能夠確定List里面放的是什么類型,可能是A也可能是B。

具體化:

當一個類型能夠在運行時態被完整的表現,我們就稱為其是可以具體化的,舉一個例子:

List就不是一個可以具體化的類型,因為它在運行時態被擦除成List!所以當處理一些需要運行時檢測類型的操作的時候(如instanceof)就要特別注意。

究竟哪些類型是可具體化的呢;

1)、原始類型,如int;

2)、非參數化的類和接口;

3)、非限定的參數化類型,如List, Map

4)、Raw類型,如 List,ArrayList

5)、所有由可具體化類型組成的數組,如Number[],List[]

哪些是不可具體化的類型呢

1)、類型變量,如T;

2),參數化類型,如List

3),有限定的參數化類型,如List;

具體哪些操作需要注意區分是否是具體化類型:

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

亚洲午夜精品一区二区_中文无码日韩欧免_久久香蕉精品视频_欧美主播一区二区三区美女