最新Java?泛型中的通配符講解
本文內容如下:
1、 什么是類型擦除
2、常用的 ?, T, E, K, V, N的含義
3、上界通配符 < ?extends E>
4、下界通配符 < ?super E>
5、什么是PECS原則
6、通過一個案例來理解 ?和 T 和 Object 的區(qū)別
一、什么是類型擦除?
我們說Java的泛型是偽泛型,那是因為泛型信息只存在于代碼編譯階段,在生成的字節(jié)碼中是不包含泛型中的類型信息的,使用泛型的時候加上類型參數,在編譯器編譯的時候會去掉,這個過程為類型擦除。
泛型是Java 1.5版本才引進的概念,在這之前是沒有泛型的,但是因為類型擦除特性,讓泛型代碼能夠很好地和之前版本的代碼兼容。
我們來看個案例
(圖1)
因為這里泛型定義為Integer類型集合,所以添加String的時候在編譯時期就會直接報錯。
那是不是就一定不能添加了呢?答案是否定的,我們可以通過Java泛型中的類型擦除特點及反射機制實現。
如下
public static void main(String[] args) throws Exception { ArrayList<Integer> list = new ArrayList(); list.add(6); //反射機制實現 Class<? extends ArrayList> clazz = list.getClass(); Method add = clazz.getDeclaredMethod("add", Object.class); add.invoke(list, "歡迎關注:后端元宇宙"); System.out.println("list = " + list); }
運行結果
list = [6, 歡迎關注:后端元宇宙]
二、案例實體準備
這里先建幾個實體,為后面舉例用
Animal類
@Data @AllArgsConstructor public class Animal { /** * 動物名稱 */ private String name; /** * 動物毛色 */ private String color; }
Pig類
:Pig是Animal的子類
public class Pig extends Animal{ public Pig(String name,String color){ super(name,color); } }
Dog類
: Dog也是Animal的子類
public class Dog extends Animal { public Dog(String name,String color){ super(name,color); } }
三、常用的 ?, T, E, K, V, N的含義
我們在泛型中使用通配符經??吹絋、F、U、E,K,V其實這些并沒有啥區(qū)別,我們可以選 A-Z 之間的任何一個字母都可以,并不會影響程序的正常運行。
只不過大家心照不宣的在命名上有些約定:
- T (Type) 具體的Java類
- E (Element)在集合中使用,因為集合中存放的是元素
- K V (key value) 分別代表java鍵值中的Key Value
- N (Number)數值類型
- ? 表示不確定的 Java 類型
四、上界通配符 < ? extends E>
語法:<? extends E>
舉例:<? extends Animal> 可以傳入的實參類型是Animal或者Animal的子類
兩大原則
add
:除了null之外,不允許加入任何元素!get
:可以獲取元素,可以通過E或者Object接受元素!因為不管存入什么數據類型都是E的子類型
示例
public static void method(List<? extends Animal> lists){ //正確 因為傳入的一定是Animal的子類 Animal animal = lists.get(0); //正確 當然也可以用Object類接收,因為Object是頂層父類 Object object = lists.get(1); //錯誤 不能用?接收 ? t = lists.get(2); // 錯誤 lists.add(new Animal()); //錯誤 lists.add(new Dog()); //錯誤 lists.add(object); //正確 除了null之外,不允許加入任何元素! lists.add(null); }
五、下界通配符 < ? super E>
語法: <? super E>
舉例 :<? super Dog> 可以傳入的實參的類型是Dog或者Dog的父類類型
兩大原則
add
:允許添加E和E的子類元素!get
:可以獲取元素,但傳入的類型可能是E到Object之間的任何類型,也就無法確定接收到數據類型,所以返回只能使用Object引用來接受!如果需要自己的類型則需要強制類型轉換。
示例
public static void method(List<? super Dog> lists){ //錯誤 因為你不知道?到底啥類型 Animal animal = lists.get(0); //正確 只能用Object類接收 Object object = lists.get(1); //錯誤 不能用?接收 ? t = lists.get(2); //錯誤 lists.add(object); //錯誤 lists.add(new Animal()); //正確 lists.add(new Dog()); //正確 可以存放null元素 lists.add(null); }
六、什么是PECS原則?
PECS原則:生產者(Producer)使用extends,消費者(Consumer)使用super。
原則
- 如果想要獲取,而不需要寫值則使用" ? extends T "作為數據結構泛型。
- 如果想要寫值,而不需要取值則使用" ? super T "作為數據結構泛型。
示例-
public class PESC { ArrayList<? extends Animal> exdentAnimal; ArrayList<? super Animal> superAnimal; Dog dog = new Dog("小黑", "黑色"); private void test() { //正確 Animal a1 = exdentAnimal.get(0); //錯誤 Animal a2 = superAnimal.get(0); //錯誤 exdentAnimal.add(dog); //正確 superAnimal.add(dog); } }
示例二
Collections集合工具類有個copy方法,我們可以看下源碼,就是PECS原則。
public static <T> void copy(List<? super T> dest, List<? extends T> src) { int srcSize = src.size(); if (srcSize > dest.size()) throw new IndexOutOfBoundsException("Source does not fit in dest"); if (srcSize < COPY_THRESHOLD || (src instanceof RandomAccess && dest instanceof RandomAccess)) { for (int i=0; i<srcSize; i++) dest.set(i, src.get(i)); } else { ListIterator<? super T> di=dest.listIterator(); ListIterator<? extends T> si=src.listIterator(); for (int i=0; i<srcSize; i++) { di.next(); di.set(si.next()); } } }
我們按照這個源碼簡單改造下
public class CollectionsTest { /** * 將源集合數據拷貝到目標集合 * * @param dest 目標集合 * @param src 源集合 * @return 目標集合 */ public static <T> void copy(List<? super T> dest, List<? extends T> src) { int srcSize = src.size(); for (int i = 0; i < srcSize; i++) { dest.add(src.get(i)); } } public static void main(String[] args) { ArrayList<Animal> animals = new ArrayList(); ArrayList<Pig> pigs = new ArrayList(); pigs.add(new Pig("黑豬", "黑色")); pigs.add(new Pig("花豬", "花色")); CollectionsTest.copy(animals, pigs); System.out.println("dest = " + animals); } }
運行結果
dest = [Animal(name=黑豬, color=黑色), Animal(name=花豬, color=花色)]
七、通過一個案例來理解 ?和 T 和 Object 的區(qū)別
1、實體轉換
我們在實際開發(fā)中,經常進行實體轉換,比如SO轉DTO,DTO轉DO等等,所以需要一個轉換工具類。
如下示例
/** * 實體轉換工具類 * * TODO 說明該工具類不能直接用于生產,因為為了代碼看去清爽點,我少了一些必要檢驗,所以如果直接拿來使用可以會在某些場景下會報錯。 */ public class EntityUtil { /** * 集合實體轉換 * * @param target 目標實體類 * @param list 源集合 * @return 裝有目標實體的集合 */ public static <T> List<T> changeEntityList(Class<T> target, List<?> list) throws Exception { if (list == null || list.size() == 0) { return null; } List<T> resultList = new ArrayList<T>(); //用Object接收 for (Object obj : list) { resultList.add(changeEntityNew(target, obj)); } return resultList; } /** * 實體轉換 * * @param target 目標實體class對象 * @param baseTO 源實體 * @return 目標實體 */ public static <T> T changeEntity(Class<T> target, Object baseTO) throws Exception{ T obj = target.newInstance(); if (baseTO == null) { return null; } BeanUtils.copyProperties(baseTO, obj); return obj; } }
使用工具類示例
private void changeTest() throws Exception { ArrayList<Pig> pigs = new ArrayList(); pigs.add(new Pig("黑豬", "黑色")); pigs.add(new Pig("花豬", "花色")); //實體轉換 List<Animal> animals = EntityUtil.changeEntityList(Animal.class, pigs); }
這是一個很好的例子,從這個例子中我們可以去理解 ?和 T 和 Object的使用場景。
我們先以集合轉換
來說
public static <T> List<T> changeEntityListNew(Class<T> target, List<?> list);
首先其實我們并不關心傳進來的集合內是什么對象,我們只關系我們需要轉換的集合內是什么對象,所以我們傳進來的集合就可以用List<?>
表示任何對象的集合都可以。
返回呢,這里指定的是Class<T>,也就是返回最終是List<T>
集合。
再以實體轉換
方法為例
public static <T> T changeEntityNew(Class<T> target, Object baseTO)
同樣的,我們并不關心源對象是什么,我們只關心需要轉換的對象,只需關心需要轉換的對象為T
。
那為什么這里用Object上面用?呢,其實上面也可以改成List<Object> list
,效果是一樣的,上面List<?> list
在遍歷的時候最終不就是用Object接收的嗎
?和Object的區(qū)別
?類型不確定和Object作用差不多,好多場景下可以通用,但?可以縮小泛型的范圍,如:List<? extends Animal>,指定了范圍只能是Animal的子類,但是用List<Object>
,沒法做到縮小范圍。
總結
- 只用于讀功能時,泛型結構使用<? extends T>
- 只用于寫功能時,泛型結構使用<? super T>
- 如果既用于寫,又用于讀操作,那么直接使用<T>
- 如果操作與泛型類型無關,那么使用<?>
到此這篇關于Java 泛型中的通配符的文章就介紹到這了,更多相關Java 泛型通配符內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
springboot如何實現異步響應請求(前端請求超時的問題解決)
這篇文章主要給大家介紹了關于springboot如何實現異步響應請求(前端請求超時的問題解決)的相關資料,文中通過實例代碼介紹的非常詳細,對大家學習或者使用springboot具有一定的參考學習價值,需要的朋友可以參考下2023-01-01springboot+HttpInvoke?實現RPC調用的方法
RPC框架大家或多或少都用過,出自于阿里系的就有dubbo,HSF,sofaRPC等,今天通過本文給大家介紹springboot+HttpInvoke?實現RPC調用的方法,感興趣的朋友一起看看吧2022-03-03Java?Hutool工具包中HttpUtil的日志統(tǒng)一打印及統(tǒng)一超時時間配置
Hutool是一個Java基礎工具類,對文件、流、加密解密、轉碼、正則、線程、XML等JDK方法進行封裝,組成各種Util工具類,這篇文章主要給大家介紹了關于Java?Hutool工具包中HttpUtil的日志統(tǒng)一打印及統(tǒng)一超時時間配置的相關資料,需要的朋友可以參考下2024-01-01