你是否真正理解了泛型、通配符、类型擦除
泛型結(jié)🐕
- 前言
- 一、使用反射繞過編譯時的類型檢查
- 二、為什么要引入泛型的呢?
- 三、泛型的使用方式
- 1.泛型類
- 2.泛型方法
- 3.泛型接口
- 四、通配符的使用
- 1.通配符上限<? extends T>
- 2.下界通配符 < ? super E>
- 3. T 和 ? 的區(qū)別
- 五、類型擦除
你是否還不知道為什么要引入泛型?
你是否還不知道怎么使用泛型?
你是否還不知道類型擦除?
你是否還不知道怎么使用通配符?
那么讀上幾分鐘,讓你的人生不再遺憾?哈撒給😁
前言
泛型是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();//獲取add類,因為反射會將類的屬性、構(gòu)造函數(shù)、方法等成為一個個的對象Method add = clazz.getDeclaredMethod("add", Object.class);//執(zhí)行invoke方法,向集合中添加數(shù)據(jù)add.invoke(list,str);System.out.println(list);
至于原理我已經(jīng)在反射一文講清楚了。該文是講泛型的,反射就不多bb了。
二、為什么要引入泛型的呢?
在JDK1.5之前,如果想要實現(xiàn)一個通用的方法,那么就需要使用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可以實現(xiàn)方法的通用,但是有個很大的弊端
當我向數(shù)組中加入整型和字符串時,在獲取數(shù)據(jù)時,轉(zhuǎn)換為String類型失敗,當然都能明白這個應(yīng)該轉(zhuǎn)換為Integer類型
但是如果數(shù)據(jù)量較大時,是不是很難避免類型轉(zhuǎn)換異常
所以為了代碼得到重(chong)用等等一系列的原因,泛型便出現(xiàn)了
三、泛型的使用方式
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(); }四、通配符的使用
我們經(jīng)常會遇見這幾種通配符,如T E K V ?,那么它們都是什么意思呢?
其實這幾個字符是約定俗成的,你可以使用A-Z的任何一個字符,但這樣的話,不太容易理解語義。
T:表示一個Java類型
?: 表示不確定的 java 類型
K V : java鍵值中的Key Value
E :代表Element
或許不是很好理解,那么看完下面的例子你就明白了
1.通配符上限<? extends T>
現(xiàn)在我自定義四個類,分別是Car、BusCar、SportCar、Airport
其中BusCar和SportCar繼承自Car,Airport與其它幾個類無任何關(guān)系
現(xiàn)在我要對Car類型的子類型車做出統(tǒng)一操作
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);}
發(fā)現(xiàn)ArrayList<BusCar>向ArrayList< Car>類型轉(zhuǎn)換失敗了,那這是為什么呢? 我們都知道BusCar是直接可以轉(zhuǎn)換為Car類的;子類->父類
其實這也很簡單,ArrayList<BusCar>和ArrayList< Car>是不同的類型,我們都知道泛型都有安全類型檢測;假如說上面的可以轉(zhuǎn)換,那豈不是我也可以向其中添加SportsCar了,甚至Car本身都能添加,這樣就亂套了。(本來是一群BusCar,突然來了一個SportsCar,這個SportsCar還不被群毆死呀)
重點來了
所以可以使用?
再次運行
這樣以來也可以保證類型的安全,如果在這種情況下加入其他類型的就會報錯。
雖然可以這樣使用,但是如果我直接向其中只加入Airport類會怎么樣呢(Airport不是Car的子類)
這樣以來是不是有很大的問題,本來start方法只是用來只執(zhí)行Car類或者Car子類的;突然跑來一個無關(guān)的Airport(飛機)類,而且還執(zhí)行成功了,是不是很荒唐,所以通配符上限就來了。
<? extends T> 表名該類型必須是T類或者T的子類,T就代表向上的極限值。
如果再使用與Car無關(guān)的類,就會報錯;
2.下界通配符 < ? super E>
表示該類型必須是E本身或者E的父類(超類),上限是Object
3. T 和 ? 的區(qū)別
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) {} //接口方法@Overridepublic 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對象,因為反射會把屬性,構(gòu)造函數(shù),方法等分為一個個的對象Method add = clazz.getDeclaredMethod("add", Object.class);//執(zhí)行invoke操作,也就是添加str到list中add.invoke(list,str);System.out.println(list);使用反射繞過了編譯,其實Java的泛型是偽泛型,意思是在編譯期間,所有的泛型信息都會被擦除;比如上面我定義的泛型是List< Integer >類型,但是到了編譯過后就變成了List,JVM只看到了List,看不到任何泛型的信息;
所以在編譯器要盡可能的發(fā)現(xiàn)錯誤;但仍無法避免類型轉(zhuǎn)換異常
總結(jié)
以上是生活随笔為你收集整理的你是否真正理解了泛型、通配符、类型擦除的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 面试官:序列化和反序列化为什么要实现Se
- 下一篇: 带你深入理解值传递(点进来才知道它是一篇