@
目錄- 一、簡介
- 二、泛型的使用
- 2.1 泛型類
- 2.2 泛型介面
- 2.3 泛型方法
- 2.4 泛型通配符
- 2.5 泛型與可變引數
- 2.6 靜態方法與泛型
- 三、泛型的優點
- 四、型別擦除
- 案例應用
一、簡介
泛型的本質是為了引數化型別(在不創建新的型別的情況下,通過泛型指定的不同型別來控制形參具體限制的型別),也就是說在泛型使用程序中,
操作的資料型別被指定為一個引數,這種引數型別可以用在類、介面和方法中,分別被稱為泛型類、泛型介面、泛型方法,
泛型:把型別明確的作業推遲到創建物件或呼叫方法的時候才去明確的特殊的型別
Java泛型設計原則:只要在編譯時期沒有出現警告,那么運行時期就不會出現ClassCastException例外.
Java 的泛型
Java 泛型的引數只可以代表類,不能代表個別物件,由于 Java 泛型的型別引數之實際型別在編譯時會被消除,所以無法在運行時得知其型別引數的型別,
Java 編譯器在編譯泛型時會自動加入型別轉換的編碼,故運行速度不會因為使用泛型而加快,Java 允許對個別泛型的型別引數進行約束,包括以下兩種形式(假設 T 是泛型的型別引數,C 是一般類、泛類,或是泛型的型別引數):T 實作介面 I ,T 是 C ,或繼承自 C ,一個泛型類不能實作Throwable介面,
------------------------------------------------------------<百度百科> ---------------------------------------------------------
二、泛型的使用
參考原文:https://blog.csdn.net/s10461/article/details/53941091
2.1 泛型類
泛型類:就是把泛型定義在類上,用戶使用該類的時候,才把型別明確下來.
通過泛型可以完成對一組類的操作對外開放相同的介面,最典型的就是各種容器類,如:List、Set、Map,
/**
* 該類為一個泛型類
* @param <T> :在實體化泛型類時,必須指定 型別引數T 的具體型別
*/
public class Generic<T> {
// 這個成員變數的型別為T,T的型別由外部指定
private T value;
// 泛型構造方法形參value的型別也為T,T的型別由外部指定
public Generic(T value) {
this.value = https://www.cnblogs.com/popo33/p/value;
}
//泛型方法getValue的回傳值型別為T,T的型別由外部指定
public T getValue() {
return value;
}
}



注意:
- 泛型的型別引數只能是型別別,不能是簡單型別,
- 不能對確切的泛型型別使用
instanceof操作,如下面的操作是非法的,編譯時會出錯,
if(ex_num instanceof Generic<Number>){ }
- 泛型類派生出的子類
-
子類將泛型型別確定
-
子類泛型型別不確定:繼續使用父類定義的泛型
-
2.2 泛型介面
泛型介面與泛型類的定義及使用基本相同,泛型介面常被用在各種類的生產器中,可以看一個例子:
/**
* 泛型介面
* @param <T>
*/
public interface Generator<T> {
public T next();
}
當實作泛型介面的類,未傳入泛型實參時:
/**
* 實作泛型介面的類
* 未傳入泛型實參時,與泛型類的定義相同,在宣告類的時候,需將泛型的宣告 T 也一起加到類中
* 即:class FruitGenerator<T> implements Generator<T>{
* 如果不宣告泛型,如:class FruitGenerator implements Generator<T>,編譯器會報錯:cannot resolve symbol T
* @param <T>
*/
public class FruitGenerator<T> implements Generator<T>{
@Override
public T next() {
return null;
}
}
當實作泛型介面的類,傳入泛型實參時:
/**
* 傳入泛型實參時:
* 定義一個生產器實作這個介面,雖然我們只創建了一個泛型介面 Generator<T>
* 但是我們可以為T傳入無數個實參,形成無數種型別的 Generator介面,
* 在實作類實作泛型介面時,如已將泛型型別傳入實參型別,則所有使用泛型的地方都要替換成傳入的實參型別
* 即:Generator<T>,public T next();中的的T都要替換成傳入的String型別,
*/
public class FruitGenerator implements Generator<String>{
private String[] fruits = new String[]{"apple","banana","peach"};
@Override
public String next() {
Random r = new Random();
return fruits[r.nextInt(3)];
}
}
2.3 泛型方法
泛型類,是在實體化類的時候指明泛型的具體型別;
泛型方法,是在呼叫方法的時候指明泛型的具體型別 ,
就只能調物件與型別無關的方法,不能呼叫物件與型別有關的方法
/**
* 泛型方法的基本介紹
* @param tClass 傳入的泛型實參
* @return T 回傳值為T型別
* 說明:
* 1)public 與 回傳值中間<T>非常重要,可以理解為宣告此方法為泛型方法,
* 2)只有宣告了<T>的方法才是泛型方法,泛型類中的使用了泛型的成員方法并不是泛型方法,
* 3)<T>表明該方法將使用泛型型別T,此時才可以在方法中使用泛型型別T,
* 4)與泛型類的定義一樣,此處T可以隨便寫為任意標識,常見的如T、E、K、V等形式的引數常用于表示泛型,
*/
public <T> T genericMethod(Class<T> tClass)throws InstantiationException ,
IllegalAccessException{
T instance = tClass.newInstance();
return instance;
}
public class GenericTest {
//這個類是個泛型類,在上面已經介紹過
public class Generic<T>{
private T key;
public Generic(T key) {
this.key = key;
}
//我想說的其實是這個,雖然在方法中使用了泛型,但是這并不是一個泛型方法,
//這只是類中一個普通的成員方法,只不過他的回傳值是在宣告泛型類已經宣告過的泛型,
//所以在這個方法中才可以繼續使用 T 這個泛型,
public T getKey(){
return key;
}
/**
* 這個方法顯然是有問題的,在編譯器會給我們提示這樣的錯誤資訊"cannot reslove symbol E"
* 因為在類的宣告中并未宣告泛型E,所以在使用E做形參和回傳值型別時,編譯器會無法識別,
public E setKey(E key){
this.key = keu
}
*/
}
/**
* 這才是一個真正的泛型方法,
* 首先在public與回傳值之間的<T>必不可少,這表明這是一個泛型方法,并且宣告了一個泛型T
* 這個T可以出現在這個泛型方法的任意位置.
* 泛型的數量也可以為任意多個
* 如:public <T,K> K showKeyName(Generic<T> container){
* ...
* }
*/
public <T> T showKeyName(Generic<T> container){
System.out.println("container key :" + container.getKey());
//當然這個例子舉的不太合適,只是為了說明泛型方法的特性,
T test = container.getKey();
return test;
}
//這也不是一個泛型方法,這就是一個普通的方法,只是使用了Generic<Number>這個泛型類做形參而已,
public void showKeyValue1(Generic<Number> obj){
Log.d("泛型測驗","key value is " + obj.getKey());
}
//這也不是一個泛型方法,這也是一個普通的方法,只不過使用了泛型通配符?
//同時這也印證了泛型通配符章節所描述的,?是一種型別實參,可以看做為Number等所有類的父類
public void showKeyValue2(Generic<?> obj){
Log.d("泛型測驗","key value is " + obj.getKey());
}
/**
* 這個方法是有問題的,編譯器會為我們提示錯誤資訊:"UnKnown class 'E' "
* 雖然我們宣告了<T>,也表明了這是一個可以處理泛型的型別的泛型方法,
* 但是只宣告了泛型型別T,并未宣告泛型型別E,因此編譯器并不知道該如何處理E這個型別,
public <T> T showKeyName(Generic<E> container){
...
}
*/
/**
* 這個方法也是有問題的,編譯器會為我們提示錯誤資訊:"UnKnown class 'T' "
* 對于編譯器來說T這個型別并未專案中宣告過,因此編譯也不知道該如何編譯這個類,
* 所以這也不是一個正確的泛型方法宣告,
public void showkey(T genericObj){
}
*/
public static void main(String[] args) {
}
}
2.4 泛型通配符
在泛型中并沒有像我們面向物件的繼承結構,想要使用任意的泛型型別,我們可以使用通配符!
?號通配符表示可以匹配任意型別,任意的Java類都可以匹配
帶有子類限定的可以從泛型讀取(? extend T)
帶有超類限定的可以從泛型寫入(? super T)
如果引數之間的型別有依賴關系,或者回傳值是與引數之間有依賴關系的,那么就使用泛型方法
如果沒有依賴關系的,就使用通配符,通配符會靈活一些.
2.5 泛型與可變引數
什么是可變引數,了解可變引數 參考:https://blog.csdn.net/w605283073/article/details/91902705
再看一個泛型方法和可變引數的例子:
public <T> void printMsg( T... args){
for(T t : args){
Log.d("泛型測驗","t is " + t);
}
}
printMsg("111",222,"aaaa","2323.4",55.55);
2.6 靜態方法與泛型
靜態方法有一種情況需要注意一下,那就是在類中的靜態方法使用泛型:靜態方法無法訪問類上定義的泛型;如果靜態方法操作的參考資料型別不確定的時候,必須要將泛型定義在方法上,
即:如果靜態方法要使用泛型的話,必須將靜態方法也定義成泛型方法 ,
public class StaticGenerator<T> {
....
....
/**
* 如果在類中定義使用泛型的靜態方法,需要添加額外的泛型宣告(將這個方法定義成泛型方法)
* 即使靜態方法要使用泛型類中已經宣告過的泛型也不可以,
* 如:public static void show(T t){..},此時編譯器會提示錯誤資訊:
"StaticGenerator cannot be refrenced from static context"
*/
public static <T> void show(T t){
}
}
三、泛型的優點
-
代碼更加簡潔【不用強制轉換】
-
程式更加健壯【只要編譯時期沒有警告,那么運行時期就不會出現ClassCastException例外】
-
可讀性和穩定性【在撰寫集合的時候,就限定了型別】
四、型別擦除
什么是型別擦除?
泛型是提供給javac編譯器使用的,它用于限定集合的輸入型別,讓編譯器在源代碼級別上,即擋住向集合中插入非法資料
但編譯器編譯完帶有泛型的java程式后,生成的class檔案中將不再帶有泛型資訊,以此使程式運行效率不受到影響,這個程序稱之為“擦除“,
案例應用
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/181805.html
標籤:Java
