java基礎(3)-----泛型
1.概述(什麼是泛型?)
泛型,即“引數化型別”,顧名思義,將具體的型別引數化,在呼叫的時候再傳入具體的型別
2.一個簡單的例子
public class GenericTest { public static void main(String[] args) { List list = new ArrayList(); list.add("qqyumidi"); list.add("corn"); list.add(100); for (int i = 0; i < list.size(); i++) { String name = (String) list.get(i); // 1 System.out.println("name:" + name); } } }
定義了一個List型別的集合,先向其中加入了兩個字串型別的值,隨後加入一個Integer型別的值。這是完全允許的,因為此時list預設的型別為Object型別。在之後的迴圈中,由於忘記了之前在list中也加入了Integer型別的值或其他編碼原因,很容易出現類似於//1中的錯誤。因為編譯階段正常,而執行時會出現“java.lang.ClassCastException”異常。因此,導致此類錯誤編碼過程中不易發現。
在如上的編碼過程中,我們發現主要存在兩個問題:
1).當我們將一個物件放入集合中,集合不會記住此物件的型別,當再次從集合中取出此物件時,改物件的編譯型別變成了Object型別,但其執行時型別任然為其本身型別。
2).因此,//1處取出集合元素時需要人為的強制型別轉化到具體的目標型別,且很容易出現“java.lang.ClassCastException”異常。
那麼有沒有什麼辦法可以使集合能夠記住集合內元素各型別,且能夠達到只要編譯時不出現問題,執行時就不會出現“java.lang.ClassCastException”異常呢?答案就是使用泛型。
3.特性
泛型只是在編譯階段有效,執行階段會進行去泛型,也就是編譯成class之後會去泛型化,屬於假泛型
public class GenericTest { public static void main(String[] args) { Box<String> name = new Box<String>("corn"); Box<Integer> age = new Box<Integer>(712); System.out.println("name class:" + name.getClass()); // com.qqyumidi.Box System.out.println("age class:" + age.getClass()); // com.qqyumidi.Box System.out.println(name.getClass() == age.getClass()); // true } }
由此,我們發現,在使用泛型類時,雖然傳入了不同的泛型實參,但並沒有真正意義上生成不同的型別,傳入不同泛型實參的泛型類在記憶體上只有一個,即還是原來的最基本的型別(本例項中為Box),當然,在邏輯上我們可以理解成多個不同的泛型型別。
究其原因,在於Java中的泛型這一概念提出的目的,導致其只是作用於程式碼編譯階段,在編譯過程中,對於正確檢驗泛型結果後,會將泛型的相關資訊擦出,也就是說,成功編譯過後的class檔案中是不包含任何泛型資訊的。泛型資訊不會進入到執行時階段。
4.自定義泛型介面、泛型類、泛型方法
4.1泛型類
泛型型別用於類的定義中,被稱為泛型類。通過泛型可以完成對一組類的操作對外開放相同的介面。最典型的就是各種容器類,如:List、Set、Map。
一個最普通的泛型類:
//此處T可以隨便寫為任意標識,常見的如T、E、K、V等形式的引數常用於表示泛型
//在例項化泛型類時,必須指定T的具體型別
public class Generic<T>{
//key這個成員變數的型別為T,T的型別由外部指定
private T key;
public Generic(T key) { //泛型構造方法形參key的型別也為T,T的型別由外部指定
this.key = key;
}
public T getKey(){ //泛型方法getKey的返回值型別為T,T的型別由外部指定
return key;
}
}
//泛型的型別引數只能是類型別(包括自定義類),不能是簡單型別
//傳入的實參型別需與泛型的型別引數型別相同,即為Integer.
Generic<Integer> genericInteger = new Generic<Integer>(123456);
//傳入的實參型別需與泛型的型別引數型別相同,即為String.
Generic<String> genericString = new Generic<String>("key_vlaue");
Log.d("泛型測試","key is " + genericInteger.getKey());
Log.d("泛型測試","key is " + genericString.getKey());
12-27 09:20:04.432 13063-13063/? D/泛型測試: key is 123456
12-27 09:20:04.432 13063-13063/? D/泛型測試: key is key_vlaue
4.2泛型介面
泛型介面與泛型類的定義及使用基本相同。泛型介面常被用在各種類的生產器中,可以看一個例子:
//定義一個泛型介面
public interface Generator<T> {
public T next();
}
當實現泛型介面的類,未傳入泛型實參時:
/**
* 未傳入泛型實參時,與泛型類的定義相同,在宣告類的時候,需將泛型的宣告也一起加到類中
* 即:class FruitGenerator<T> implements Generator<T>{
* 如果不宣告泛型,如:class FruitGenerator implements Generator<T>,編譯器會報錯:"Unknown class"
*/
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", "Pear"};
@Override
public String next() {
Random rand = new Random();
return fruits[rand.nextInt(3)];
}
}
4.3泛型萬用字元
我們知道Ingeter
是Number
的一個子類,同時在特性章節中我們也驗證過Generic<Ingeter>
與Generic<Number>
實際上是相同的一種基本型別。那麼問題來了,在使用Generic<Number>
作為形參的方法中,能否使用Generic<Ingeter>
的例項傳入呢?在邏輯上類似於Generic<Number>
和Generic<Ingeter>
是否可以看成具有父子關係的泛型型別呢?
為了弄清楚這個問題,我們使用Generic<T>
這個泛型類繼續看下面的例子:
public void showKeyValue1(Generic<Number> obj){
Log.d("泛型測試","key value is " + obj.getKey());
}
Generic<Integer> gInteger = new Generic<Integer>(123);
Generic<Number> gNumber = new Generic<Number>(456);
showKeyValue(gNumber);
// showKeyValue這個方法編譯器會為我們報錯:Generic<java.lang.Integer>
// cannot be applied to Generic<java.lang.Number>
// showKeyValue(gInteger);
通過提示資訊我們可以看到Generic<Integer>
不能被看作為`Generic<Number>
的子類。由此可以看出:同一種泛型可以對應多個版本(因為引數型別是不確定的),不同版本的泛型類例項是不相容的。
回到上面的例子,如何解決上面的問題?總不能為了定義一個新的方法來處理Generic<Integer>
型別的類,這顯然與java中的多臺理念相違背。因此我們需要一個在邏輯上可以表示同時是Generic<Integer>
和Generic<Number>
父類的引用型別。由此型別萬用字元應運而生。
我們可以將上面的方法改一下:
public void showKeyValue1(Generic<?> obj){
Log.d("泛型測試","key value is " + obj.getKey());
}
型別萬用字元一般是使用?代替具體的型別實參,注意了,此處’?’是型別實參,而不是型別形參 。重要說三遍!此處’?’是型別實參,而不是型別形參 ! 此處’?’是型別實參,而不是型別形參 !再直白點的意思就是,此處的?和Number、String、Integer一樣都是一種實際的型別,可以把?看成所有型別的父類。是一種真實的型別。
可以解決當具體型別不確定的時候,這個萬用字元就是 ? ;當操作型別時,不需要使用型別的具體功能時,只使用Object類中的功能。那麼可以用 ? 萬用字元來表未知型別。
型別萬用字元上限和型別萬用字元下限
public class GenericTest {
public static void main(String[] args) {
Box<String> name = new Box<String>("corn");
Box<Integer> age = new Box<Integer>(712);
Box<Number> number = new Box<Number>(314);
getData(name);
getData(age);
getData(number);
//getUpperNumberData(name); // 1
getUpperNumberData(age); // 2
getUpperNumberData(number); // 3
}
public static void getData(Box<?> data) {
System.out.println("data :" + data.getData());
}
public static void getUpperNumberData(Box<? extends Number> data){
System.out.println("data :" + data.getData());
}
}
此時,顯然,在程式碼//1處呼叫將出現錯誤提示,而//2 //3處呼叫正常。
型別萬用字元上限通過形如Box<? extends Number>形式定義,相對應的,型別萬用字元下限為Box<? super Number>形式,其含義與型別萬用字元上限正好相反