1. 程式人生 > >java高階特性之泛型

java高階特性之泛型

泛型擦除

反射機制

自定義泛型類

自定義型別方法

java泛型別相關知識

1 為什麼使用泛型

解決元素儲存時候的安全性問題,同時解決獲取集合元素的時候型別強轉的問題。

提高程式碼的重用率。

例如編寫一個泛型類,而我們不去關心類的具體型別,而用T來代表其變化的型別。T不能代表基本資料型別。

泛型注意:

在使用泛型的表示式中,如果兩邊都使用了泛型,那麼兩邊的泛型必須一致。

如果表示式只有一邊使用了泛型,那麼表示式的另一邊可以不使用泛型。

(如果任何型別的資料都可以新增到集合中,造成集合型別不安全,另外讀取出來的物件可能需要轉換,使用繁瑣,容易出現)

泛型中的幾個概念:

以ArrayList為例,<>念做 typeof

ArrayList中的E,稱為型別引數變數

ArrayList中的Integer被稱為實際型別引數

整個ArrayLIst泛型型別

整個ArrayList稱為引數化的型別ParameterizedType.

ClassCastException(類轉換異常)

如下程式碼:

ArrayList a1 = new ArrayList();

a1.add(dog);

a1.add(cat);

Dog temp = a1.get(0);

System.out.println(a1.get(0));

Dog temp = (Dog) a1.get(0);

//dog是Dog類的一個例項

首先定義了一個ArrayList 型別的集合,然後往集合裡面添加了Dog型別的物件以及Cat型別的物件,這裡是完全允許的,因為所有的型別集合都預設為Object類,在javac階段也沒有問題,但是在java階段(執行階段)就會出現型別轉換的異常,ClassCastException。

分析:

1.當我們將一個物件放入集合的時候,集合不會記住物件的型別,當再次從集合中取出物件的時候,該物件的編譯型別為Object型別,而執行時型別任何為其本身的型別。

2.若想使用該物件,必須要做強制轉換。

【摘自某部落格】

【泛型,JDK1.5新加入的,解決資料型別的安全性問題,其主要原理是在類宣告時通過一個標識表示類中某個屬性的型別或者是某個方法的返回值及引數型別。這樣在類宣告或例項化時只要指定好需要的具體的型別即可。】

【安全隱患的理解】:

在java1.5之前,沒有泛型的情況下,通過對型別Object 的引用來實現引數的“任意化”,任意化帶來的確定就是要做顯示的強制型別轉換,而且要求開發者必須對實際引數型別預知的情況下進行,而且對於強制型別轉換錯誤的情況下,javac不會顯示錯誤,在執行的時候才會異常,導致jvm崩潰。這是一個安全的隱患。

參考程式碼TestGenerics.java

泛型類

泛型類的宣告:

class/interface name


//當通過物件調泛型方法時,指明泛型方法的型別。

public <E> List<E> fromArrayToList(E[] e,List<E> list){

for(E xx : e){

list.add(xx);

}

return list;

}

//

List<Integer> list3 = order.fromArrayToList(in, list2);

泛型與繼承:

若類B是類A的子類,那麼List < B >和 List< A >沒有繼承關係。

若想在泛型中使用繼承,那麼可以使用萬用字元?

List< B > 和List< A >都是List< ? >的子類

List< ? extends A>可以存放類A及其子類的

List< ? super A> 可以存放類A及其父類

使用萬用字元的集合:

List< ? > list :可以從使用萬用字元的集合list中獲取資料,其元素型別為Object。不可以往萬用字元的集合中list新增資料除了null之外。

泛型擦除

我們發現,在使用泛型類時,雖然傳入了不同的泛型實參,但並沒有真正意義上生成不同的型別, 傳入不同泛型實參的泛型類在記憶體上只有一個,即還是原來的最基本的型別(new 出來的物件是何種型別,那麼它在記憶體上就是什麼型別), 當然,在邏輯上我們可以理解成多個不同的泛型型別。

究其原因,在於Java中的泛型這一概念提出的目的,導致其只是作用於程式碼編譯階段,在編譯過程中,對於正確檢驗泛型結果後,會將泛型的相關資訊擦出,也就是說,成功編譯過後的class檔案中是不包含任何泛型資訊的。泛型資訊不會進入到執行時階段。

對此總結成一句話:泛型類在邏輯上看以看成是多個不同的型別,實際上都是相同的型別。【泛型擦出】

java核心技術一書中的觀點:
jvm中沒有泛型型別的物件,所有的物件都是普通物件。無論何時定義了一個泛型型別,都自動的提供了一個相應的原始型別,(raw type),原始型別的名字就是刪除了型別引數後的泛型型別名,擦除(erased)型別變數,並替代類限定型別。
翻譯泛型表示式:
由於泛型擦除機制,編譯器會將泛型方法翻譯為2條虛擬機器指定
1呼叫原始的方法
2對於返回的Object型別的資料強制型別轉換為泛型型別。
總之需要記住有關java泛型轉換的事實:
1,虛擬機器中沒有泛型,只有普通的類和方法
2,所有的型別引數都是由他們的限定型別替換
3,橋方法被合成用來保持多型
4,為保持型別的安全性,必要時插入強制型別轉換。

反射和泛型

Class類是泛型的,String.class實際上是Class類的物件,也是唯一的物件。
型別引數十分有用,因為它允許Class方法的返回型別更加具有針對性。

JVM中的泛型型別資訊
java泛型的卓越特性就是泛型擦除,被擦除的型別仍然可以通過反射API獲取泛型資訊。
如下方法:
public static < T extends Comparable < ? supper T>> T min(T[] a)

通過反射API可以獲取:
在SE5.0之後,新的介面Type,這個介面下包含的子型別:
Class:描述具體型別
TypeVariable介面:描述型別變數(Textends Comparable < ? supper T>)
WildcardType介面:描述萬用字元(如?supper T)
ParameterizedType介面:描述泛型類或者介面型別(Comparable < ? supper T>)
GenericArrayType介面:描述泛型陣列 T[] a