1. 程式人生 > >12.Java泛型的原理,基本和高階應用

12.Java泛型的原理,基本和高階應用

Java泛型的原理,基本和高階應用

1.體驗泛型

(1)Jdk1.5以前的集合類中存在什麼問題?

1.5之前的jdk,我們使用泛型的方式是這樣,當我們給這個集合中存放了值後,我們想要取值的時候,我們不知道取出來是什麼型別,所以返回get返回的是Object,暗示我們想要把它當作int型看待,於是我們使用強制型別轉換,雖然編譯通過了,但是執行還是會出現錯誤,因為我們取的值不是Integer型別的,無法轉換。所以說,之前的集合存在這樣兩個問題:

a.取值時需要型別轉換b.有時候轉換不成功

所以,我們使用jdk1.5的泛型改寫一下就是下面這個情況:給集合指定型別

同理,我們看我們之前的一個例子,

我們在學習反射的時候對構造方法進行反射操作,因為我們執行期間無法確定構造方法例項化後的型別是什麼,所以我們需要強制型別轉換成String型別的,現在我們通過泛型的改寫:

我們可以看到我們使用了泛型以後,規定了我們的構造方法類的資料型別為String型別,返回結果無需要在進行強制型別轉換了。

那都有哪些類可以使用泛型呢,我們可以通過檢視幫助文件,在類後面標註<T>的就可以使用泛型,比如:

沒有使用泛型時,只要是物件,不論是什麼型別的物件,都可以儲存同一個集合中,使用泛型集合,可以將一個集合中的元素限定為一個特定的型別,集合中只能儲存同一個型別的物件,這樣更安全,並且當從集合中獲取一個物件的時候,編譯器也可以知道這個物件的型別,不需要在進行強制型別轉換。

JDK1.5中如果你還可以按照原來的方式將各種不同型別的資料存在一個集合中,但此時編譯器會報一個Uncheck的異常。

2.泛型的內部原理以及更深的應用。

1)泛型是提供給javac編譯器使用的,可以限定集合中的輸入型別,讓編譯器擋住源程式中的非法輸入,編譯器編譯時型別說明的集合時會除掉“型別”資訊,使執行效率不受影響,對於引數化的泛型型別,getClass方法返回值和原始型別完全一樣,由於編譯生成的位元組碼會去掉泛型的型別資訊,只要能跳過編譯器,就可以使某個泛型集合中加入其它型別的資料,

例如,先反射得到集合,再呼叫其add方法即可。

首先我們來了解一下什麼叫做“去型別化”:

我們可以看到兩個不同泛型型別的集合,在通過編譯以後,輸出的是指向同一個位元組碼,結果為true,說明什麼意思呢,說明了使用泛型的集合在經過編譯器編譯的時候會告訴編譯器我的集合裡面裝的東西是什麼型別,你只能裝我這個型別,算是一個執行前的判斷。如果不符合型別則編譯不通過。完成這樣一個功能後,編譯器會把這個資訊記住,但是括號中的型別他會預設的去掉,此時他們返回的位元組碼就一致了,所以返回的為true,這就掉去型別化。

既然如此,經過編譯後的集合已經和普通集合沒有什麼區別,那麼我們就可以通過反射機制操作這個集合,儘管在編譯之前它是一個Integer型別的,但經過編譯以後,已經去型別化了,所以我們可以呼叫其add方法新增一個String型別的資料:看截圖:

(2)ArrayList<E>類定義和ArrayList<Integer>類引用中涉及如下術語:

·整個成為ArrayList<E>泛型型別

·ArrayList<E>中的E稱為型別變數或者型別引數

·整個ArrayList<Integer>稱為引數化的型別

·ArrayList<Integer>中的integer稱為型別引數的實力或者實際型別引數

·ArrayList<Integer>中的<>念為typeof   ArrayList稱為原始型別

(3)引數化型別與原始型別的相容性

·引數化型別可以引用一個原始型別的物件,編譯會報告警報,例如:

Collection<String> c = new Vector();

·原始型別也可以引用一個引數化型別的物件,編譯會報告警報,例如:

Collection c = new Vector<String>;

(4)引數化型別不考慮型別引數的繼承關係

//錯誤,不寫Object可以,但寫了就等於明知規範 Object String不相等

Vector<String>  v  = new Vector<Object>;

//也錯誤,一個Vector既然只能裝String,不可能裝Integer又怎麼能賦值給Object,呢?

Vector<Object> v = new Vector<String>;//也錯誤

(5)在建立陣列例項的時候,陣列的元素不能使用引數化的型別:

Vector<Integer> vactorlist[] = new Vector<Integer>[10];

思考:下面的程式碼會報錯嗎?

Vector v1 = new Vector<String>();

Vector<Object> v = v1;

答:不會報錯,第一行是說將引數化型別給原始型別,沒問題,第二行是將原始型別給引數化型別也沒錯。和Vector<Object> v = new Vector<String>是不相同的。

3.泛型的“?”萬用字元應用

1)萬用字元的使用-

問題:定義一個方法,該方法用於打印出任意引數化型別的集合中的所有資料:

2)萬用字元的擴充套件

·限定萬用字元的上邊界:

正確:Vector<? Extends Number> x = new Vector<Integer>();

錯誤:Vector<? Extends Number> y = new Vector<String>();

第一行正確,這一行的意思是說我定義的引數化型別x繼承Number這個類,而這個類中包含了Integer, 所以這樣的賦值是可行的;二行反之。String類屬於Number這個類

·限定萬用字元的下邊界:

正確:Vector<? Super Integer> x = new Vector<Number>();

錯誤:Vector<? Super Integer> x = new Vector<Byte>();

第一行正確,第一行的意思是說,我定義的引數化型別最低限度為Integer型別的資料,Number包含了Integer,Integer之上,所以可以賦值,但是第二行,Byte不在Integer之上,所以賦值失敗。

提示:限定萬用字元總是包括自己。

4.泛型集合類的綜合案例

下面我們做一個例子,例項化一個泛型的HashMap集合,列印集合的所有keyvalue

我們需要用到HashMap的一個方法:

for(Map.Entry<String, Integer> entry : hashMap.entrySet()){

System.out.println(entry.getKey()+" : "+entry.getValue());

}

能夠寫出下面的程式碼即代表掌握了Java的泛型集合類:

對在jsp頁面中也經常要對Set或者Map集合進行迭代

<s:forEach items = ${map} var=entry>

  ${entry.key}  :  ${entry.value}

</s:forEach>

5.自定義泛型方法及其應用

(1) 定義泛型的方法

·Java的泛型方法沒有C++模板函式的功能那麼強大,Java中的如下程式碼無法通過編譯

<T> add (T x,T y){

Return(T)(x+y)

//return null;

}

·交換兩個陣列中的兩個元素的位置的泛型方法語法定義如下:

Static <T> void swap(T[] a,int i,int j){

T t = a[i];

a[i] = a[j];

a[j] = t;

}

·用於放置泛型的型別的引數的尖括號應該出現在方法的其他所有修飾符之後和在方法的返回型別之前,也就是緊鄰返回值之前,按照慣例,型別引數通常用單個的大寫字母表示。

·只有引用型別才能作為泛型方法的實際引數,swapnew int[3],3,5;語法會報告編譯錯誤

·除了在應用泛型時間可以使用extends限定符,在定義泛型時也可以使用extends限定符,,例如:Class.getAnnotation()方法的定義。並且可以在&來制定多個邊界,如<V extends  Serivlizable & cloneable> void method(){}

·普通方法、構造方法和靜態方法都可以使用泛型,編譯器也不允許建立型別變數的陣列。

·也可以用型別變量表示一樣,稱為引數化的一場,可以用於方法的throws列表中,但是不能用於cathc的字句中。

Private static <T extends Exception>sayHello(throws T){

Try{

}catch(Exception e){

Throw(T)e;

}

}

·在泛型中可以同時有多個型別引數,在定義它們的尖括號中用逗號分,例如:

Public static <K,V> VgetValue(K key){return map.get(key)}

6.泛型方法的練習題

(1) 編寫一個泛型方法,自動將Object型別的物件轉換成其他型別。

(2) 定義一個方法,可以將任意型別的陣列中的所有元素填充為相應型別的某個物件。

(3) 採用自定義泛型方法的方式打印出任意引數化型別的集合中的所有內容:

(在這種情況下,前面的萬用字元方案要比泛型方法更有效,當一個型別變數用來表達兩個引數之間或者引數和返回值之間的關係時,即同一個型別變數在方法簽名的兩處被使用或者型別變數在方法的程式碼中被使用而不是僅在簽名的時候被使用,才需要使用泛型方法)

(4) 定義一個方法,把任意引數型別的集合中的資料安全的複製到相應型別的陣列中。

(5) 定義一個方法,把任意引數型別的陣列中的資料安全的複製到相應型別的陣列中。

7.型別引數的型別推斷

· 編譯器判斷泛型方法的實際型別引數的過程稱為型別推斷,型別推斷是相對於知覺推斷的,其實現方法是一種非常複雜的過程。

· 根據呼叫泛型方法時實際傳遞的引數型別或者返回值的型別來推斷,具體規則如下:

(1) 當某個型別變數只在整個引數列表中的所有引數和返回值中的一處被應用了,那麼根據呼叫方法時該出的實際應用型別來確定,這很容易憑感覺推斷出阿里,即直接根據呼叫方法時傳遞的引數型別或者返回值的決定泛型引數的型別 例如:

Swap(new String[3].3,4)   -------static <E> void swap(E[] a ,int I ,int j)

(2) 當某個型別變數在整個引數列表中的所有引數和返回值中的多處被應用了,如果呼叫方法時這多處的實際應用型別都對應同一種類型來確定,例如:

Add(3,5)   ------  static <T> add(T a,T b);

(3)當某個型別變數在整個引數列表中的所有引數和返回值中的多處被使用了,如果呼叫方法時這多處的實際應用型別對應到了不同的型別,且沒有使用返回值,這時候去多個引數中的最大交集型別,例如:

Fill(new Integer[3] 3,5)  ----static <T>  void  fill(T[]  a,T v)

(4)當某個型別變數在整個引數列表中的所有引數和返回值中的多處被應用了,如果呼叫方法時這多處的實際應用型別對應到了不同的型別,並且使用返回值,這時候有限考慮返回值的型別,例如,下面的語句實際上對應的型別是Integer,編譯會報告錯誤,將變數x的型別改為float,對比eclipse報告的錯誤提示,接著再將變數x型別改為number 則沒有這個錯誤:

Int x = (3,3.5f) ----static <T> T add(T a,T b);

(5)引數型別的型別推斷具有傳遞性,下面第一種情況推斷實際引數型別為Object 編譯沒有問題。而第二種情況則根據引數化的vector類例項將型別變數直接確定為String型別編譯會出現問題:

Copy(new Integer[5] new String[5])  ---- static <T> void copy(T[] a T[] b)

7.定義泛型型別

· 如果類的例項物件中的多處都要用到同一個泛型引數,即這些地方引用的泛型型別要保持同一個實際型別時,這個時候就要採用泛型型別的方式進行定義,也就是類級別的泛型,語法格式如下:

import java.util.Set;

publicclass GeneralDao<T> {

publicvoid add(T x) {

}

public T findById(int id) {

returnnull;

}

publicvoid delete(T obj) {

}

publicvoid delete(int id) {

}

publicvoid update(T obj) {

}

public T findByUserName(String name) {

returnnull;

}

public Set<T> findByConditions(String where) {

returnnull;

}

}

當呼叫的時候就可以這麼寫:

· 類級別的泛型是根據引用該類名時指定的型別資訊來引數化型別變數的,例如,如下兩種方式,都可以:

(1) GeneralDao<String> dao = null;

(2) New GeneralDao<String>();

注意:

1)在對泛型型別進行引數化的時候,型別引數的例項必須是引用型別,不能為基本型別。

2)當一個變數被宣告為泛型時,只能被例項變數和方法呼叫(還有內嵌型別),而不能被靜態變數

8.通過反射獲得泛型的實際型別引數(難)

相關推薦

12.Java原理基本高階應用

Java泛型的原理,基本和高階應用 1.體驗泛型 (1)Jdk1.5以前的集合類中存在什麼問題? 在1.5之前的jdk,我們使用泛型的方式是這樣,當我們給這個集合中存放了值後,我們想要取值的

java中<?><T>區別

類型 父類 定義 ext 方法 oid tor 接收 通配符 public static void printColl(ArrayList<?> al){ Iterator<?> it = al.iterator();

java中<?><T>有什麽區別?

n) add 簽名 object 表達 類型變量 ring 類型 arr public static void printColl(ArrayList<?> al){ Iterator<?> it = al.iterat

java-陣列以及列舉

列舉: java不允許用=為列舉常量賦值,列舉中的構造方法必須為private修飾 列舉中values方法將列舉型別的成員以陣列的形式返回 toString方法能夠返回列舉常量名 ordinal方法返回enumeration宣告中列舉常量的位置 列舉是一個類,可以有自己的屬性和方法並且實現

java原理詳解

一、Java泛型的實現方法:型別擦除 前面已經說了,Java的泛型是偽泛型。為什麼說Java的泛型是偽泛型呢?因為,在編譯期間,所有的泛型資訊都會被擦除掉。正確理解泛型概念的首要前提是理解型別擦出(type erasure)。 Java中的泛型基本上都是在編譯器這個層次來實現的。在生成的Java

Java 中? super T? extends T的區別

原文連結          李璟([email protected]) 經常發現有List<? super T>、Set<? extends T>的宣告,是什麼意思呢?<? super T>

Java原理

最近一個東西困惑我:Java泛型,Lambda是否是編譯器行為?為什麼Java 若干版本以後就支援了呢?如果說在jdk1.5之前使用了泛型,還能使用嗎? 看一段介紹 Java泛型被引入的好處是安全簡單。 在Java SE 1.5之前,沒有泛型的情況的下,通過對

Java中? super T? extends T的區別

來自:併發程式設計網 - ifeve.com 譯者:李璟 連結:http://ifeve.com/difference-between-super-t-and-extends-t-in-java/ 原文:http://stackoverflow.com/quest

Java中<?> <? extends Object>的異同分析

相信很多人和我一樣,接觸Java多年,卻仍舊搞不清楚 `Java` 泛型中 ``和 ``的相似和不同。但是,這應該是一個比較高階大氣上檔次的Question, 在我們進行深入的探討之前,有必要對Java泛型有一個基礎的瞭解。詳細請看上一篇文章! [重溫Java泛型,帶你更深入地理解它,更好的使用它!](htt

java基本介紹使用

java現在開始深入學習java的泛型了,以前一直只是在集合中簡單的使用泛型,根本就不明白泛型的原理和作用。泛型在java中,是一個十分重要的特性,所以要好好的研究下。一、泛型的基本概念 泛型的定義:泛型是JDK 1.5的一項新特性,它的本質是參數化類型(Parameterized Type)的應用,也就是說

java的理解為什麼擦出後還可以得到

java泛型的理解,和為什麼擦出後,還可以得到 開篇 泛型的來源和影響 泛型擦除 泛型擦除了,為什麼反射時還可以得到 開篇 泛型的使用和例子不說了,太多同類型的文章,自己搜搜,本文主要說 泛型的來源和影響 泛

java 在類介面方法上的應用

雖然我在泛型的使用這塊,應用的地方不是很多,但是還是要總結一下的,這一篇文章主要是從使用的角度,對泛型進行介紹。 如果一個類有一個或者多個型別的變數,那麼這個類就是泛型類,這些型別變數是類的型別引數。下邊這個類是一個簡單的java類,有一個屬性t,它的型別是Object 方

java(一)、基本介紹使用

 轉載地址  http://m.blog.csdn.net/article/details?id=7864531 現在開始深入學習java的泛型了,以前一直只是在集合中簡單的使用泛型,根本就不明白泛型的原理和作用。泛型在java中,是一個十分重要的特性,所以要好好的研究

封裝設定屬性一家人都要整整齊齊系列(1) JAVA的實現原理

1.基本學過JAVA的人都知道一點泛型,明白常出現的位置和大概怎麼使用。在類上為:class 類名<T> {}  在方法上為:public <T> void 方法名 (T x){}就不再贅述了。  2.泛型就是將型別變成了引數去傳入,使得可以使用的型別

Java(一) 基本使用介紹

轉載自:http://blog.csdn.net/lonelyroamer/article/details/7864531 現在開始深入學習java的泛型了,以前一直只是在集合中簡單的使用泛型,根本就不明白泛型的原理和作用。泛型在java中,是一個十分重要的特性,所以要

C#高階語法之約束型別安全、逆變協變(思想原理

一、為什麼使用泛型? 泛型其實就是一個不確定的型別,可以用在類和方法上,泛型在宣告期間沒有明確的定義型別,編譯完成之後會生成一個佔位符,只有在呼叫者呼叫時,傳入指定的型別,才會用確切的型別將佔位符替換掉。 首先我們要明白,泛型是泛型,集合是集合,泛型集合就是帶泛型的集合。下面我們來模仿這List集合看一下下面

Java,通配符C#對照

size list ack ace arr 類型通配符 語法 ++ net c#的泛型沒有類型通配符,原因是.net的泛型是CLR支持的泛型,而Java的JVM並不支持泛型,僅僅是語法糖,在編譯器編譯的時候都轉換成object類型 類型通配符在java中表示的是泛型

深入--java的繼承實現、擦除

部分 end father 沒有 接口 子類 set int nal 泛型實現類: package generic; /** * 泛型父類:子類為“富二代”:子類的泛型要比父類多 * 1,保留父類的泛型-->子類為泛型類

JAVA基本使用

end ++ rc.d param details file println super turn Java1.5版本號推出了泛型,盡管這層語法糖給開發者帶來了代碼復用性方面的提升,可是這只是是編譯器所做的一層語法糖,在真正生成的字節碼中,這類信息卻被擦除了。筆者發

Java之集合初探(二)Iterator(叠代器)collections打包/解包(裝箱拆箱)(Generic)comparable接口

基本 generate 等於 框架 ring bin list() each 是否 Iterator(叠代器) 所有實現了Collection接口的容器都有一個iterator方法, 用來返回一個實現了Iterator接口的對象 Iterator對象稱作叠代器, 用來