關於Java泛型深入理解小總結

風痕影默發表於2014-09-12

1、何為泛型

首先泛型的本質便是型別引數化,通俗的說就是用一個變數來表示型別,這個型別可以是String,Integer等等不確定,表明可接受的型別,原理類似如下程式碼

int pattern; //宣告一個變數未賦值,pattern可以看作是泛型
pattern = 4;
pattern = 5;//4和5就可以看作是String和Integer

泛型的具體形式見泛型類、泛型方法

  *泛型類形式如下

class Test<T>
{
    private T t;
    Test(T t)
    {
        this.t = t;
    }
    public  T getT()
    {
        return t;
    }

    public void setT(T t)
    {
        this.t = t;
    }
}

  *泛型方法舉例程式碼如下

public <T> void show()
{
    operation about T...
}

泛型引數型別宣告必須在返回型別之前

2、為何要引入泛型,即泛型與Object的優勢

由於泛型可以接受多個引數,而Object經過強制型別轉換可以轉換為任何型別,既然二者都具有相同的作用,為何還要引進泛型呢?

解答:泛型可以把使用Object的錯誤提前到編譯後,而不是執行後,提升安全性。以下用帶泛型的ArrayList和不帶泛型的Arraylist舉例說明

程式碼1:

ArrayList al = new ArrayList();
al.add("hello");
al.add(4);//自動裝箱
String s1 = (String)al.get(0);
String s2 = (String)al.get(1);//在編譯時沒問題,但在執行時出現問題

首先宣告無泛型的ArrayList時,其預設的原始型別是Object陣列,既然為Object型別,就可以接受任意資料的賦值,因此編譯時沒有問題,但是在執行時,Integer強轉成String,肯定會出現ClassCastException.因此泛型的引入增強了安全性,把類轉換異常提前到了編譯時期。

3、型別擦除和原始型別

  *型別擦除的由來

在JAVA的虛擬機器中並不存在泛型,泛型只是為了完善java體系,增加程式設計師程式設計的便捷性以及安全性而建立的一種機制,在JAVA虛擬機器中對應泛型的都是確定的型別,在編寫泛型程式碼後,java虛擬中會把這些泛型引數型別都擦除,用相應的確定型別來代替,代替的這一動作叫做型別擦除,而用於替代的型別稱為原始型別,在型別擦除過程中,一般使用第一個限定的型別來替換,若無限定則使用Object.

  *對泛型類的翻譯

泛型類(不帶泛型限定)程式碼:

class Test<T>
{
    private T t;
    public void show(T t)
    {

    }
}

虛擬機器進行翻譯後的原始型別:

class Test
{
    private Object t;
    public void show(Object t)
    {
        
    }
}

泛型類(帶泛型限定)程式碼: 

class Test<? extends Comparable>
{
    private T t;
    public void show(T t)
    {

    }
}

虛擬機器進行翻譯後的原始型別:

class Test
{
    private Comparable t;
    public void show(Comparable t)
    {
        
    }
}

 *泛型方法的翻譯

class Test<T>
{
    private T t;
    public void show(T t)
    {

    }
}

class TestDemo extends Test<String>
{
    private String t;
    public void show(String t)
    {
        
    }
}

由於TestDemo繼承Test<String>,但是Test在型別擦除後還有一個public void Show(Object t),這和那個show(String t)出現過載,但是本意卻是沒有show(Object t)的,

因此在虛擬機器翻譯泛型方法中,引入了橋方法,及在型別擦除後的show(Object t)中呼叫另一個方法,程式碼如下:

public void show(Object t)
{
    show((String) t);
}

4、泛型限定

  *泛型限定是通過?(萬用字元)來實現的,表示可以接受任意型別,那一定有人有疑問,那?和T(二者單獨使用時)有啥區別了,其實區別也不是很大,僅僅在對引數型別的使用上。

例如:

public void print(ArrayList<?> al)
{
    Iterator<?> it = al.iterator();
    while(it.hasNext())
    {
        System.out.println(in.next());
    }
}

public
<T> void print(ArrayList<T> al) { Iterator<T> it = al.iterator(); while(it.hasNext()) { T t = it.next(); //區別就在此處,T可以作為型別來使用,而?僅能作為接收任意型別 System.out.println(t); } }

  *? extends SomeClass  這種限定,說明的是隻能接收SomeClass及其子類型別,所謂的“上限”

  *? super SomeClass 這種限定,說明只能接收SomeClass及其父類型別,所謂的“下限”

一下舉例? extends SomeClass說明一下這類限定的一種應用方式

由於泛型引數型別可以表示任意型別的類型別,若T要引用compareTo方法,如何保證在T類中定義了compareTo方法呢?利用如下程式碼:

public <T extends Comparable> shwo(T a, T b)
{
    int num = a.compareTo(b);
}

此處用於限定T型別繼承自Comparable,因為T型別可以呼叫compareTo方法。

  *可以有多個型別限定,例如:

<T extends Comparable & Serializable>

這種書寫方式為何把comparable寫在前邊?因為由於型別擦除的問題,原始型別是由Comparable替換的,因此寫在前邊的是類中存在此型別的泛型方法放在前邊,避免呼叫方法時型別的強制轉換,提高效率。

class Test<T extends Comparable & Serializable>
{
    private T lower;
    private T upper;

    public Test(T first, T second) //此處是利用Comparable的方法,因此把Comparable寫在前邊,型別擦除後為Comparable,若為Serializable,還得用強制型別轉換,否則不能使用compareTo方法。
    {
        int a = first.compareTo(second);
        ...
    }
}

  *關於泛型型別限定的“繼承”誤區

總有些人誤把型別的限定當作繼承,比如:

//型別是這樣的
<Student extends Person>
//然後出現此類錯誤
ArrayList<Person> al = new ArrayList<Student>();

此處的<Person>, <Student>作為泛型的意思是ArrayList容器的接收型別,用一個簡單的例子來說明

ArrayList是一個大型養殖場,<Person>表明的是他能夠接收動物,而上邊的new語句生成的是一個只能夠接收豬的養殖場(ArrayList<Student>),即把一個大型養殖場建造成了一個養豬場,若是繼承的大型養殖場肯定是還能接受狗、羊....的,但是現在建造成了養豬場,那還能接受別的動物麼?所以這肯定是錯誤的用法!簡而言之,泛型new時兩邊的型別引數必須一致。

5、泛型的一些基本規則約束

  *泛型的型別引數必須為類的引用,不能用基本型別(int, short, long, byte, float, double, char, boolean)

  *泛型是型別的引數化,在使用時可以用作不同型別(此處在說泛型類時會詳細說明)

  *泛型的型別引數可以有多個,程式碼舉例如下:

public <T, E> void show()
{    
    coding operation.....    
}                

  *泛型可以使用extends, super, ?(萬用字元)來對型別引數進行限定

  *由於型別擦除,執行時型別查詢只適用於原始型別,比如instanceof、getClass()、強制型別轉換,a instanceof (Pair<Employe>),在型別擦除後便是 a instanceof Pair,因此以上執行的一些操作在虛擬機器中操作都是對原始型別進行操作,無論寫的多麼虛幻,都逃不出型別擦除,因為在虛擬機器種並不存在泛型。

  *不能建立引數化型別的陣列

例如寫如下程式碼:

Pair<String>[] table = new Pair<String>[10]; //ERROR
//讓Object[] t指向table
Object[] t = table;
//向t中新增物件
t[0] = new Pair<Employe>();
//關鍵錯誤之處
String s = table[0];

由於Object可以接收任何型別,在裡邊存入 new Pari<Employe>時,沒有任何問題,但是當取出的時候會出現ClassCastException,因此不能建立引數化型別陣列。

  *不能例項化型別變數,及不能出現以下的類似程式碼

T t = new T();
//
T.Class

因為在型別擦除後,便是Object t = new Object();與用意不符合,即本意肯定不是要呼叫Object.

  *不能再靜態域或方法中出現引數型別

例如如下錯誤程式碼

class Test<T>
{
    private static T example;  //error
    public static void showExample() //error
    {
        action about T...
    }
    public static T showExample() //error
    {
        action about T....    
     }
}    

首先方法是一個返回型別為T的普通方法,而非泛型方法,這和在靜態方法中使用非靜態引數是一樣的,靜態方法是先於物件而存在記憶體中的,因此在編譯的時候,T的型別無法確定,一定會出現“Cannot make a static reference to a non_static reference”這樣類似的錯誤。

但是這樣的程式碼就是正確的

class Test<T>
{public static <T> T show()
    {
        action
    }
}

因為此處的靜態方法是泛型方法,可以使用.

  *不能丟擲或捕獲泛型類的例項

    +不能丟擲不能捕獲泛型類物件

    +泛型類不能擴充套件Throwable,注意是類不能繼承Throwable,型別引數的限定還是可以的。

    +catch子句不能使用型別變數,如下程式碼

try
{
    ....
}
catch(T e) //error
{
    ...
}

  *型別擦除後的衝突注意

例如:

class Pair<T>
{
    public boolean equals(T value) //error
    {
        ....
    }
}

此處的錯誤的原因不能存在同一個方法,在型別擦除後,Pair的方法為,public boolean equals(Object value),這與從Object.class中繼承下來的equals(Object obj)衝突。

  *一個類不能成為兩個介面型別的子類,而這兩個介面是同一介面的不同引數化。

例如:

class Calendar implements coparable<Calendar>{}

class GregorianCalendar extends Calendar implements Comparable<GregorianCalendar>{} //error

當型別擦除後,Calendar實現的是Comparable,而GregorianCalendar繼承了Calendar,又去實現Comparable,必然出錯!

———————————————————————————————————————————————————————————————————————————————

 先總結到此處。

相關文章