Java 泛型的學習與應用。
所謂的泛型,就是變數型別的引數化。泛型是java1.5中引入的一個重要特徵,通過引入泛型,可以使編譯時型別安全,執行時更少丟擲ClassCastException的可能。提到引數化,就會想到定義方法是由形參,然後呼叫該方法傳入實參。泛型的變數型別的引數化就是將原來傳入的具體的型別引數化,這就像方法中的變數引數。使用泛型的時候如果不提供引數型別,即泛型類沒有引數化,系統會警告,此時型別為Object。
為什麼使用泛型
使用泛型的典型例子,是在集合中的泛型使用。
在使用泛型前,存入集合中的元素可以是任何型別的,當從集合中取出時,所有的元素都是Object型別,需要進行向下的強制型別轉換,轉換到特定的型別。
List<Integer> myIntList = new LinkedList<Integer>(); // 1'
myIntList.add(new Integer(0)); // 2'
Integer x = myIntList.iterator().next(); // 3'
第一行的List型別說明,編譯器會為我們檢查型別的正確性。這樣,程式碼的可讀性和健壯性也會增強。
上述程式碼中<>包含的是形式型別引數(formal type parameters),與一般的型別一樣,可以在整個類的宣告中使用。當類被呼叫的時候,此時會傳入具體的實際型別引數(actual type argument)
List<Integer>
,那麼所有的E將會被Integer代替。注: 泛型型別引數只能被類或介面型別賦值,不能被原生資料型別(byte,short,int,long,float,double,char,boolean等8種)賦值,原生資料型別需要使用對應的包裝類(Byte,Short,Integer,Long,Float,Double,Character,Boolean)。
-
形式型別引數(formal type parameters)命名:
儘量使用單個的大寫字母(多個泛型型別會加上數字,如T1,T2),容器集合使用E,代表element,map中用K-keys,V-values。 -
特性:泛型只會在編譯階段有效。栗子如下:
List<String> stringArrayList = new ArrayList<String>(); List<Integer> integerArrayList = new ArrayList<Integer>(); Class classStringArrayList = stringArrayList.getClass(); Class classIntegerArrayList = integerArrayList.getClass(); if(classStringArrayList.equals(classIntegerArrayList)){ Log.d("泛型測試","型別相同"); }
通過上面的例子可以證明,在編譯之後程式會採取去泛型化的措施。也就是說Java中的泛型,只在編譯階段有效。在編譯過程中,正確檢驗泛型結果後,會將泛型的相關資訊擦出,並且在物件進入和離開方法的邊界處新增型別檢查和型別轉換的方法。也就是說,泛型資訊不會進入到執行時階段。
總結:泛型型別在邏輯上看以看成是多個不同的型別,實際上都是相同的基本型別。
泛型的應用
三種方式:泛型類,泛型介面,泛型方法。
泛型類
1.1 一個普通的泛型類
public class FXParent<T1, T2> {
private T1 t1;
private T2 t2;
public T1 getT1() {
return t1;
}
public void setT1(T1 t1) {
this.t1 = t1;
}
public T2 getT2() {
return t2;
}
public void setT2(T2 t2) {
this.t2 = t2;
}
}
new一個parent泛型類 ,T1,T2為型別形參,可以接收任何型別,建立的時候傳入Integer,String限定接收型別。
FXParent<Integer, String> fxParent = new FXParent<>();
1.2 繼承泛型類
public class FXChild<T1, T2, T3> extends FXParent<T1, T2> {
private T3 t3;
public T3 getT3() {
return t3;
}
public void setT3(T3 t3) {
this.t3 = t3;
}
}
泛型介面
2.1 一個泛型介面
/**
* 作者 Superman
* 日期 2018/11/22 10:03.
* 檔案 FanXingPractice
* 描述 泛型介面
*/
public interface FXParentInterface<T1, T2> {
void setData1(T1 t1);
void setData2(T2 t2);
T1 getData1();
T2 getData2();
}
2.1 實現泛型介面
/**
* 作者 Superman
* 日期 2018/11/22 9:55.
* 檔案 FanXingPractice
* 描述 繼承泛型類別 子類
*/
public class FXChild<T1, T2, T3> extends FXParent<T1, T2> implements FXParentInterface<T1, T2> {
private T3 t3;
public T3 getT3() {
return t3;
}
public void setT3(T3 t3) {
this.t3 = t3;
}
@Override
public void setData1(T1 t1) {
}
@Override
public void setData2(T2 t2) {
}
@Override
public T1 getData1() {
return getT1();
}
@Override
public T2 getData2() {
return getT2();
}
}
當實現泛型介面,卻沒有傳入泛型實參的時候,在宣告類時,需要將泛型的宣告也加到類之中去。
如果不宣告就會編譯報錯:
/ * 即:cclass FXTest<T1, T2> implements FXParentInterface<T1, T2>
* 如果不宣告泛型,如:class FXTest implements FXParentInterface<T1,T2>,編譯器會報錯:*/
泛型方法
3.1 泛型方法建立
/*
*<T>必不可少,表示這是一個泛型方法,並且聲明瞭一個泛型T
* 這個T可以出現在這個泛型方法的任意位置.
* 泛型的數量也可以為任意多個
*/
public static <T> T dowork(T t) {
return t;
}
型別萬用字元(用?表示)
泛型的萬用字元:當不知道用何種型別來接收的時候,就可以用?。**此處’?’是型別實參,而不是型別形參 。再直白點的意思就是,此處的?和Number、String、Integer一樣都是一種實際的型別,可以把?看成所有型別的父類。是一種真實的型別。**可以解決當具體型別不確定的時候,這個萬用字元就是 ? ;當操作型別時,不需要使用型別的具體功能時,只使用Object類中的功能。那麼可以用 ? 萬用字元來表未知型別!
@Override
public void onClick(View view) {
FXTongpeifu<Integer> test1 = new FXTongpeifu<Integer>(123);
showKey(test1);
}
});
/**
*
*/
public void showKey(FXTongpeifu<?> test) {
Log.e("泛型萬用字元測試:", "" + test.getKey());
}
泛型的上限和下限
在使用泛型的時候,我們還可以為傳入的泛型型別實參進行上下邊界的限制,如:型別實參只准傳入某種型別的父類或某種型別的子類。
/**
*泛型的上限
* 傳入的型別必須為指定型別的子類
*
* @param test
*/
public void showKey2(FXTongpeifu<? extends Number> test) {
Log.e("泛型萬用字元測試:", "" + test.getKey());
}
/**
*泛型的下限
* 傳入的型別必須為指定型別的父類
*
* @param test
*/
public void showKey2(FXTongpeifu<? super Number> test) {
Log.e("泛型萬用字元測試:", "" + test.getKey());
}
後續學習補充中。。
參考部落格,文章:
https://blog.csdn.net/s10461/article/details/53941091
https://blog.csdn.net/dreamzuora/article/details/79647960
http://www.cnblogs.com/mengdd/archive/2013/01/21/2869778.html