描述
泛型,即“參數化類型”。一提到參數,最熟悉的就是定義方法時有形參,然后調用此方法時傳遞實參。
參數化類型就是將類型由原來的具體的類型參數化,類似于方法中的變量參數,此時類型也定義成參數形式(可以稱之為類型形參),然后在調用時傳入具體的類型(類型實參)。
泛型的本質是為了參數化類型(在不創建新的類型的情況下,通過泛型指定的不同類型來控制形參具體限制的類型)。也就是說在泛型使用過程中,操作的數據類型被指定為一個參數,這種參數類型可以用在類、接口和方法中,分別被稱為 泛型類、泛型接口、泛型方法。
總結來說就是:把具體的類型參數化。
泛型的作用
1、在編譯時檢查類型安全;
2、提高代碼的重用率。
特性
泛型只在編譯階段有效。
在編譯之后程序會采取去泛型化的措施。也就是說泛型只在編譯階段有效。
在編譯過程中,正確檢驗泛型結果后,會將泛型的相關信息擦出,并且在對象進入和離開方法的邊界處添加類型檢查和類型轉換的方法。也就是說,泛型信息不會進入到運行時階段。
泛型通配符 “?”
類型通配符一般是使用“?”代替具體的類型實參,注意了,此處’?’是類型實參,而不是類型形參 。
可以把 “?”看成所有類型的父類,是一種真實的類型。
可以解決當具體類型不確定的時候,這個通配符就是 ? ;當操作類型時,不需要使用類型的具體功能時,只使用Object類中的功能。那么可以用 ? 通配符來表未知類型。
public class GenericsActivity extends BaseActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_generics);
showKeyValue(new GenericsClass<>("string"));
showKeyValue(new GenericsClass<>(123));
}
//泛型通配符?
public void showKeyValue(GenericsClass<?> obj){
Log.d("泛型測試","key value is " + obj.getKey());
}
}
五、泛型類
ex:
//此處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_value");
Log.d("泛型測試","key is " + genericInteger.getKey());
Log.d("泛型測試","key is " + genericString.getKey());
泛型接口
泛型接口與泛型類的定義及使用基本相同。泛型接口常被用在各種類的生產器中,可以看一個例子:
//定義一個泛型接口
public interface Generator<T> {
public T next();
}
/**
* 未傳入泛型實參時,與泛型類的定義相同,在聲明類的時候,需將泛型的聲明也一起加到類中
* 即:class FirstGenerator<T> implements Generator<T>{
* 如果不聲明泛型,如:class FirstGenerator implements Generator<T>,編譯器會報錯:"Unknown class"
*/
public class FirstGenerator<T> implements Generator<T> {
@Override
public T makeSomeThing() {
return null;
}
}
/**
* 傳入泛型實參時:
* 定義一個生產器實現這個接口,雖然我們只創建了一個泛型接口Generator<T>
* 但是我們可以為T傳入無數個實參,形成無數種類型的Generator接口。
* 在實現類實現泛型接口時,如已將泛型類型傳入實參類型,則所有使用泛型的地方都要替換成傳入的實參類型
* 即:Generator<T>,public T makeSomeThing();中的的T都要替換成傳入的String類型。
*/
public class SecondGenerator implements Generator<String> {
private String[] arrays = new String[] {"Apple", "Banana", "Pear"};
@Override
public String makeSomeThing() {
return arrays[0];
}
}
泛型方法
1、泛型方法的基本介紹
/**
* 泛型方法的基本介紹
* @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與返回值之間的<T>必不可少,這表明這是一個泛型方法,并且聲明了一個泛型T
* 這個T可以出現在這個泛型方法的任意位置.
* 泛型的數量也可以為任意多個
* 如:public <T,K> K GenericsClass(Generic<T> container){
* ...
* }
*/
public <T> T showKeyName(GenericsClass<T> container){
Log.d("泛型測試","key value is " + container.getKey());
T test = container.getKey();
return test;
}
2、泛型方法和可變參數
public <T> void printMsg( T... args){
for(T t : args){
Log.d("泛型測試","t is " + t);
}
}
printMsg("111",222,"aaaa","2323.4",55.55);
泛型的上下邊界
在泛型方法中添加上下邊界限制的時候,必須在權限聲明與返回值之間的<T>上添加上下邊界,即在泛型聲明的時候添加
//public <T> T showKeyName(Generic<T extends Number> container),編譯器會報錯:"Unexpected bound"
public <T extends Number> T showKeyName(Generic<T> container){
System.out.println("container key :" + container.getKey());
T test = container.getKey();
return test;
}
九、JVM泛型的擦除機制
正確理解泛型概念的首要前提是理解類型擦出(type erasure)。
Java中的泛型基本上都是在編譯器這個層次來實現的。在生成的Java字節碼中是不包含泛型中的類型信息的。使用泛型的時候加上的類型參數,會在編譯器在編譯的時候去掉。這個過程就稱為類型擦除。