泛型結🐕
- 前言
- 一、使用反射繞過編譯時的型別檢查
- 二、為什么要引入泛型的呢?
- 三、泛型的使用方式
- 1.泛型類
- 2.泛型方法
- 3.泛型介面
- 四、通配符的使用
- 1.通配符上限<? extends T>
- 2.下界通配符 < ? super E>
- 3. T 和 ? 的區別
- 五、型別擦除
你是否還不知道為什么要引入泛型?
你是否還不知道怎么使用泛型?
你是否還不知道型別擦除?
你是否還不知道怎么使用通配符?
那么讀上幾分鐘,讓你的人生不再遺憾?哈撒給😁
前言
泛型是JDK1.5引入的新特性,泛型提供了編譯時型別檢測安全機制,
可能不是太好理解,下面使用代碼演示

沒錯,當你定義了泛型為Integer型別之后,試圖向集合中加入String型別,那編譯肯定不會通過,
至于怎么向Integer集合型別中加入String型別,那么就肯定要繞過編譯了!
然后你就可以想到使用什么了,沒錯,就是反射!
一、使用反射繞過編譯時的型別檢查
List<Integer> list = new ArrayList<>();
list.add(2);
String str = "a";
// list.add(str);
//實體化物件
Class<? extends List> clazz = list.getClass();
Method add = clazz.getDeclaredMethod("add", Object.class);
add.invoke(list,str);
System.out.println(list);

至于原理我已經在反射一文講清楚了,該文是講泛型的,反射就不多bb了,
二、為什么要引入泛型的呢?
在JDK1.5之前,如果想要實作一個通用的方法,那么就需要使用Object
/**
* @author:抱著魚睡覺的喵喵
* @date:2021/4/1
* @description:
*/
public class Common {
private Object[] data;
public Common(int length) {
this.data = new Object[length];
}
public Object getData(int index) {
return data[index];
}
public void addData(int index, Object data) {
this.data[index] = data;
}
}
然后就使用唄
Common common = new Common(5);
common.addData(0,"zsh");
common.addData(1,23);
String data = (String) common.getData(0);
String data2 = (String) common.getData(1);
System.out.println(data);
System.out.println(data2);

從這可以看出使用Object可以實作方法的通用,但是有個很大的弊端
每次都要進行型別的強制轉換,強制轉換時還有可能出錯,
所以為了代碼得到重(chong)用,泛型便出現了
三、泛型的使用方式
1.泛型類
public class Generic<T> {
private T data;
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
public class GenericExtends<T> extends Generic<T> {
}
class GenericExtends2<T> extends Generic<String> {
}
2.泛型方法
public class Generic<T> {
public <E> void iterator(E[] arr) {
for (E ele : arr) {
System.out.printf("%s\t",ele);
}
}
}
Generic generic = new Generic();
Integer arr[] = {1,35,56};
String[] str ={"hello"};
generic.iterator(arr);
generic.iterator(str);

3.泛型介面
public interface Alorgithm<T>{
T method();
}
四、通配符的使用
我們經常會遇見這幾種通配符,如T E K V ?,那么它們都是什么意思呢?
其實這幾個字符是約定俗成的,你可以使用A-Z的任何一個字符,但這樣的話,不太容易理解語意,
T:表示一個Java型別
?: 表示不確定的 java 型別
K V : java鍵值中的Key Value
E :代表Element
或許不是很好理解,那么看完下面的例子你就明白了
1.通配符上限<? extends T>
現在我自定義四個類,分別是Car、BusCar、SportCar、Airport
其中BusCar和SportCar繼承自Car,Airport與其它幾個類無任何關系
public class Car {
public String toString() {
return "car ---> run build";
}
}
public class BusCar extends Car{
@Override
public String toString() {
return "busCar ----> run build";
}
}
public class SportCar extends Car{
@Override
public String toString() {
return "sportCar -----> run build";
}
}
public class Airport {
public String toString() {
return "airPort ---> run build";
}
}
現在我要對Car型別的子型別車做出統一操作
public static void start(ArrayList<Car> cars) {
for (Object car : cars) {
System.out.println(car);
}
}
public static void main(String[] args) {
ArrayList<BusCar> list = new ArrayList<>();
BusCar busCar = new BusCar();
BusCar busCar2 = new BusCar();
list.add(busCar);
list.add(busCar2);
start(list);
}

發現ArrayList<BusCar>向ArrayList< Car>型別轉換失敗了,那這是為什么呢? 我們都知道BusCar是直接可以轉換為Car類的;子類->父類
其實這也很簡單,ArrayList<BusCar>和ArrayList< Car>是不同的型別,我們都知道泛型都有安全型別檢測;假如說上面的可以轉換,那豈不是我也可以向其中添加SportsCar了,甚至Car本身都能添加,這樣就亂套了,(本來是一群BusCar,突然來了一個SportsCar,這個SportsCar還不被群毆死呀)
重點來了
所以可以使用?
再次運行
這樣以來也可以保證型別的安全,如果在這種情況下加入其他型別的就會報錯,
雖然可以這樣使用,但是如果我直接向其中只加入Airport類會怎么樣呢(Airport不是Car的子類)

這樣以來是不是有很大的問題,本來start方法只是用來只執行Car類或者Car子類的;突然跑來一個無關的Airport(飛機)類,而且還執行成功了,是不是很荒唐,所以通配符上限就來了,
<? extends T> 表名該型別必須是T類或者T的子類,T就代表向上的極限值,
如果再使用與Car無關的類,就會報錯;
2.下界通配符 < ? super E>
表示該型別必須是E本身或者E的父類(超類),上限是Object
3. T 和 ? 的區別
1.有時候我們可以對 T 進行操作,但是對 ?卻 不行,比如如下這種
T car = start(); //正確
? car = start(); //錯誤
T表示一個Java型別
?表示一個不確定的Java型別
2.T可以多重限定而?不行
這個T必須是Auth和Alorgithm的共同子類;?不可以的原因是?表示的是一個不確定的型別;
public class Alu implements Alorgithm,Auth{
//這個T必須是Auth和Alorgithm的共同子類
private static<T extends Auth & Alorgithm> void test(T t) {
}
//介面方法
@Override
public Object method() {
return null;
}
}
3.通配符?可以使用超類限定而T不可以
T extends A
? extends A
? super A
五、型別擦除
List<Integer> list = new ArrayList<>();
list.add(2);
String str = "a";
list.add(str); //報錯
System.out.println(list);
這樣添加一個整型之后,再添加一個字串,肯定報錯;因為泛型在編譯期間會進行型別安全檢測
但是下面這樣就可以
List<Integer> list = new ArrayList<>();
list.add(2);
String str = "a";
//實體化物件
Class<? extends List> clazz = list.getClass();
//獲取add物件,因為反射會把屬性,建構式,方法等分為一個個的物件
Method add = clazz.getDeclaredMethod("add", Object.class);
//執行invoke操作,也就是添加str到list中
add.invoke(list,str);
System.out.println(list);
使用反射繞過了編譯,其實Java的泛型是偽泛型,意思是在編譯期間,所有的泛型資訊都會被擦除;比如上面我定義的泛型是List< Integer >型別,但是到了編譯過后就變成了List,JVM只看到了List,看不到任何泛型的資訊;
所以在編譯器要盡可能的發現錯誤;但仍無法避免型別轉換例外
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/271928.html
標籤:其他







