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

