目錄
泛型
1.?什么是泛型
2.泛型方法
3.通配符上界(泛型的協(xié)變)
4.通配符下界(泛型的逆變)
5.泛型的編譯(擦除機制)
泛型
? ? ? ? 泛型:就是讓一個類能適用于多個類型,就是在封裝數(shù)據(jù)結構時能讓封裝的類型被各種類型使用所以引入了泛型的概念,雖然有了泛型,什么數(shù)據(jù)都可以放,但是更多情況下我們還是希望他只能持有一種數(shù)據(jù)類型。所以,泛型的主要目的:指定當前的容器,要持有什么類型的對象,讓編譯器去做檢查。
1.?什么是泛型
語法格式如下:
泛型類<類型實參>變量名;//定義一個泛型類引用
new 泛型類<類型實參>(構造方法實參);//實例化一個泛型類對象
一般用<T>作為占位符 ,表示當前類是一個泛型類。Java中的泛型參數(shù)只能是引用類型,不能是基本類型,這與Java的泛型擦出機制有關。
?實例:
MyArray<Integer> list = new MyArray<Integer>();
*裸類型(Raw Type)? ? ? (這是一個泛型類但沒有帶著類型實參)?
MyArray list = new MyArray();
裸類型是為了兼容老版本的API保留機制,我們不要輕易使用。?
2.泛型方法
?泛型方法:定義一個泛型方法,我們需要在方法返回值前使用尖括號聲明一個或多個泛型參數(shù)然在方法中就可以用到聲明的泛型參數(shù)了,調用泛型方法時,我們不需要手動寫出類型,編譯器會根據(jù)你的調用,自動推導出具體類型。
靜態(tài)泛型方法:泛型類有一個局限,靜態(tài)方法和靜態(tài)屬性訪問不了類上定義的泛型參數(shù),靜態(tài)泛型方法的定義和使用與普通泛型方法一致。
泛型類和泛型方法的使用場景:
當泛型參數(shù)需要在多個方法或成員屬性間扭轉,就使用泛型類,比如:集合。
當泛型參數(shù)只需要作用于某個方法,那就使用泛型方法。
3.通配符上界(泛型的協(xié)變)
泛型類型是具有不變性的,比如下面代碼就是錯誤的:
Arraylist<Object> objectList;
ArrayList<String> stringList = new ArrayList<>();
objectList=stringList//這里會報錯
objectList.add(new Shit());
String str = stringList.get(0);
//因為我們無法將一個object對象轉化為string對象,所以在編譯層面上面的賦值就會直接報錯
?為了讓泛型變得更靈活,Java引入了通配符:?,通過下面的代碼來給大家介紹一下通配符的作用:
在不使用通配符時,因為泛型的不變性,下面這段代碼會出現(xiàn)問題,就使代碼非常不靈活。
public static double sum(List<Number> list){
double result =0;
for(Number number : list){
result += number.doubleValue();
}
return result;
}
List<Double> doubleList = new ArrayList<>();
sum(doubleList)//這里會報錯
我們可以使用通配符上界(?:extends T)來使代碼更靈活
public static double sum(List<? extends Number> list){
double result =0;
for(Number number : list){
result += number.doubleValue();
}
return result;
}
List<Double> doubleList = new ArrayList<>();
sum(doubleList)
這種寫法也被叫做泛型的協(xié)變 。
4.通配符下界(泛型的逆變)
我們還可以使用通配符下界(?:super T)來使代碼變得靈活,代碼實例如下:
class Food {
}
class Fruit extends Food {
}
class Apple extends Fruit {
}
class Plate<T> {
private T plate ;
public T getPlate() {
return plate;
}
public void setPlate(T plate) {
this.plate = plate;
}
}
public class TestDemo {
public static void main(String[] args) {
Plate<Fruit> plate1 = new Plate<>();
plate1.setPlate(new Fruit());
fun(plate1);
Plate<Food> plate2 = new Plate<>();
plate2.setPlate(new Food());
fun(plate2);
}
public static void fun(Plate<? super Fruit> temp){
// 此時可以修改??!添加的是Fruit 或者Fruit的子類
temp.setPlate(new Apple());//這個是Fruit的子類
temp.setPlate(new Fruit());//這個是Fruit的本身
//Fruit fruit = temp.getPlate(); 不能接收,這里無法確定是哪個父類
System.out.println(temp.getPlate());//只能直接輸出
}
}
通配符的優(yōu)缺點:
協(xié)變:放寬了對子類類型的泛型約束,但是缺點是不能對調用的參數(shù)進行寫入數(shù)據(jù)只能進行讀取數(shù)據(jù)。
逆變:放寬了對父類類型的泛型約束,但是缺點是不能對參數(shù)進行讀取數(shù)據(jù),只能寫入數(shù)據(jù)。
5.泛型的編譯(擦除機制)
擦除機制的實質就是,在編譯階段,Java的泛型類型可能是ArrayList<Integer>但是在java文件編譯成字節(jié)碼的過程中,泛型參數(shù)部分就被擦出了(泛型類,泛型方法的參數(shù)全部被替換成它的第一個上界或者頂級父類Object),在class文件中,無論參數(shù)是什么,JVM實際執(zhí)行的代碼類型其實是ArrayList<Object>類型,這也就引出了很多問題如下:
- 泛型參數(shù)只能是引用類型而不能是基本數(shù)據(jù)類型,因為基本數(shù)據(jù)類型無法被擦除成Object。
- 不能使用instanceof關鍵字進行泛型類型檢測,因為在運行時所以的泛型類型都是裸類型。
- 泛型類型無法實例化類型參數(shù)T a=new T(),因為在運行時無法確定T的具體類型,也不知道T是否存在無參構造器。
- 無法實例化泛型數(shù)組T[] arry =new T[2];因為泛型最后都被擦除成Object數(shù)組,在使用時很容易發(fā)生類型轉化異常,比如object轉化不成string。
擦除機制是Java為了引入泛型這個語法而不得不做出的妥協(xié)之舉,泛型語法是JDK5之后引入的,為了兼容老版本,不得不在編譯階段將泛型擦除成裸類型。但是在其他語言中,泛型的使用會非常自然且簡單安全,在編寫代碼是我們要了解泛型擦除機制,否則可能會引發(fā)很多不必要的異常。文章來源:http://www.zghlxwxcb.cn/news/detail-763613.html
類型擦除是指在運行時對于JVM而言泛型參數(shù)被擦除掉了,并不代表泛型信息消失了,才class文件中泛型信息被以其他方式進行保存,我們依然可以在運行時通過反射的手段進行泛型類型檢測。文章來源地址http://www.zghlxwxcb.cn/news/detail-763613.html
到了這里,關于【JavaSE】Java進階知識一(泛型詳解,包括泛型方法,協(xié)變,逆變,擦除機制)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!