Java泛型中通配符T、E、K、V、?的深入理解
前言
不久前,被人問(wèn)到Java 泛型中的通配符 T,E,K,V,? 是什么?有什么用?這不經(jīng)讓我有些回憶起該開始學(xué)習(xí)Java那段日子,那是對(duì)泛型什么的其實(shí)有些迷迷糊糊的,學(xué)的不這么樣,是在做項(xiàng)目的過(guò)程中,漸漸有又看到別人的代碼、在看源碼的時(shí)候老是遇見,之后就專門去了解學(xué)習(xí),才對(duì)這幾個(gè)通配符 T,E,K,V,?有所了解。
泛型有什么用?
在介紹這幾個(gè)通配符之前,我們先介紹介紹泛型,看看泛型帶給我們的好處。
Java泛型是JDK5中引入的一個(gè)新特性,泛型提供了編譯是類型安全檢測(cè)機(jī)制,這個(gè)機(jī)制允許開發(fā)者在編譯是檢測(cè)非法類型。泛型的本質(zhì)就是參數(shù)化類型,就是在編譯時(shí)對(duì)輸入的參數(shù)指定一個(gè)數(shù)據(jù)類型。
- 類型安全:編譯是檢查類型是否匹配,避免了ClassCastexception的發(fā)生。
// 非泛型寫法(存在類型轉(zhuǎn)換風(fēng)險(xiǎn))
List list1 = new ArrayList();
list1.add("a");
Integer num = (Long) list1.get(0); // 運(yùn)行時(shí)拋出 ClassCastException
// 泛型寫法(編譯時(shí)檢查類型)
List<String> list2 = new ArrayList<>();
// list.add(1); // 編譯報(bào)錯(cuò)
list2.add("a");
String str = list2.get(0); // 無(wú)需強(qiáng)制轉(zhuǎn)換
- 消除代碼強(qiáng)制類型轉(zhuǎn)換:減少了一些類型轉(zhuǎn)換操作。
// 非泛型寫法
Map map1 = new HashMap();
map1.put("user", new User());
User user1 = (User) map1.get("user");
// 泛型寫法
Map<String, User> map2 = new HashMap<>();
map2.put("user", new User());
// 自動(dòng)轉(zhuǎn)換
User user2 = map2.get("user");
3.代碼復(fù)用:可以支持多種數(shù)據(jù)類型,不要重復(fù)編寫代碼,例如:我們常用的統(tǒng)一響應(yīng)結(jié)果類。
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result<T> {
/**
* 響應(yīng)狀態(tài)碼
*/
private int code;
/**
* 響應(yīng)信息
*/
private String message;
/**
* 響應(yīng)數(shù)據(jù)
*/
private T data;
/**
* 時(shí)間戳
*/
private long timestamp;
其他代碼省略...
- 增強(qiáng)可讀性:通過(guò)類型參數(shù)就直接能看出要填入什么類型。
List<String> list = new ArrayList<>();
泛型里的通配符
我們?cè)谑褂梅盒偷臅r(shí)候,經(jīng)常會(huì)使用或者看見多種不同的通配符,常見的 T,E,K,V,?這幾種,相信大家一定不陌生,但是真的問(wèn)你他們有什么作用?有什么區(qū)別時(shí),很多人應(yīng)該是不能很好的介紹它們的,接下來(lái)我就來(lái)給大家介紹介紹。
T,E,K,V
- T(Type) T表示任意類型參數(shù),我們舉個(gè)例子
pubile class A<T>{
prvate T t;
//其他省略...
}
//創(chuàng)建一個(gè)不帶泛型參數(shù)的A
A a = new A();
a.set(new B());
B b = (B) a.get();//需要進(jìn)行強(qiáng)制類型轉(zhuǎn)換
//創(chuàng)建一個(gè)帶泛型參數(shù)的A
A<B> a = new A<B>();
a.set(new B());
B b = a.get();
- E(Element) E表示集合中的元素類型
List<E> list = new ArrayList<>();
- K(Key) K表示映射的鍵的數(shù)據(jù)類型
Map<K,V> map = new HashMap<>();
- V(Value) V表示映射的值的數(shù)據(jù)類型
Map<K,V> map = new HashMap<>();
通配符 ?
- 無(wú)界通配符 <?> 表示未知類型,接收任意類型
// 使用無(wú)界通配符處理任意類型的查詢結(jié)果
public void logQueryResult(List<?> resultList) {
resultList.forEach(obj -> log.info("Result: {}", obj));
}- 上界通配符 <? extends T> 表示類型是T或者是子類
// 使用上界通配符讀取緩存
public <T extends Serializable> T getCache(String key, Class<T> clazz) {
Object value = redisTemplate.opsForValue().get(key);
return clazz.cast(value);
}- 下界通配符 <? super T> 表示類型是T或者是父類
// 使用下界通配符寫入緩存
public void setCache(String key, <? super Serializable> value) {
redisTemplate.opsForValue().set(key, value);
}綜合示例:
import java.util.ArrayList;
import java.util.List;
public class Demo {
//實(shí)體類
class Animal {
void eat() {
System.out.println("Animal is eating");
}
}
class Dog extends Animal {
@Override
void eat() {
System.out.println("Dog is eating");
}
}
class Husky extends Dog {
@Override
void eat() {
System.out.println("Husky is eating");
}
}
/**
* 無(wú)界通配符 <?>
*/
// 只能讀取元素,不能寫入(除null外)
public static void printAllElements(List<?> list) {
for (Object obj : list) {
System.out.println(obj);
}
// list.add("test"); // 編譯錯(cuò)誤!無(wú)法寫入具體類型
list.add(null); // 唯一允許的寫入操作
}
/**
* 上界通配符 <? extends T>
*/
// 安全讀取為Animal,但不能寫入(生產(chǎn)者場(chǎng)景)
public static void processAnimals(List<? extends Animal> animals) {
for (Animal animal : animals) {
animal.eat();
}
// animals.add(new Dog()); // 編譯錯(cuò)誤!無(wú)法確定具體子類型
}
/**
* 下界通配符 <? super T>
*/
// 安全寫入Dog,讀取需要強(qiáng)制轉(zhuǎn)換(消費(fèi)者場(chǎng)景)
public static void addDogs(List<? super Dog> dogList) {
dogList.add(new Dog());
dogList.add(new Husky()); // Husky是Dog子類
// dogList.add(new Animal()); // 編譯錯(cuò)誤!Animal不是Dog的超類
Object obj = dogList.get(0); // 讀取只能為Object
if (obj instanceof Dog) {
Dog dog = (Dog) obj; // 需要顯式類型轉(zhuǎn)換
}
}
public static void main(String[] args) {
// 測(cè)試無(wú)界通配符
List<String> strings = List.of("A", "B", "C");
printAllElements(strings);
List<Integer> integers = List.of(1, 2, 3);
printAllElements(integers);
// 測(cè)試上界通配符
List<Dog> dogs = new ArrayList<>();
dogs.add(new Dog());
processAnimals(dogs);
List<Husky> huskies = new ArrayList<>();
huskies.add(new Husky());
processAnimals(huskies);
// 測(cè)試下界通配符
List<Animal> animals = new ArrayList<>();
addDogs(animals);
System.out.println(animals);
List<Object> objects = new ArrayList<>();
addDogs(objects);
}
}
我們需要清楚,這些只是我們開發(fā)過(guò)程中約定,不是強(qiáng)制規(guī)定,但遵循它們可以提高代碼的可讀性。
總結(jié)
我們?cè)诤芏鄷r(shí)候只是單純的會(huì)使用某些技術(shù),但是對(duì)它們里面許許多多常見的都是一知半解的,只是會(huì)使用確實(shí)很重要,但是如果有時(shí)間,我們不妨好好的在對(duì)這些技術(shù)進(jìn)行深入學(xué)習(xí),不僅知其然,而且知其所以然,這樣我們的技術(shù)才會(huì)不斷提升進(jìn)步。
到此這篇關(guān)于Java泛型中通配符T、E、K、V、?的文章就介紹到這了,更多相關(guān)Java泛型通配符T、E、K、V、??jī)?nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot實(shí)現(xiàn)文件記錄日志及日志文件自動(dòng)歸檔和壓縮
Logback是Java日志框架,通過(guò)Logger收集日志并經(jīng)Appender輸出至控制臺(tái)、文件等,SpringBoot配置logback-spring.xml可實(shí)現(xiàn)日志文件存儲(chǔ),本文給大家介紹了SpringBoot實(shí)現(xiàn)文件記錄日志及日志文件自動(dòng)歸檔和壓縮,需要的朋友可以參考下2025-05-05
Springboot整合Flowable6.x導(dǎo)出bpmn20的步驟詳解
這篇文章主要介紹了Springboot整合Flowable6.x導(dǎo)出bpmn20,Flowable流程引擎可用于部署B(yǎng)PMN 2.0流程定義,可以十分靈活地加入你的應(yīng)用/服務(wù)/構(gòu)架,本文給出兩種從flowable導(dǎo)出流程定義bpmn20.xml的方式,需要的朋友可以參考下2023-04-04
關(guān)于SpringBoot Actuator漏洞補(bǔ)救方案
SpringBoot Actuator模塊提供了健康檢查,審計(jì),指標(biāo)收集,HTTP 跟蹤等,是幫助我們監(jiān)控和管理SpringBoot應(yīng)用的模塊,本文將主要介紹SpringBoot Actuator漏洞的補(bǔ)救方案,需要的朋友可以參考下2023-06-06
My eclipse 端口占用(9360)問(wèn)題解決辦法
這篇文章主要介紹了My eclipse 工程發(fā)布時(shí)出現(xiàn)端口占用問(wèn)題解決辦法的相關(guān)資料,需要的朋友可以參考下2016-12-12
MP(MyBatis-Plus)實(shí)現(xiàn)樂(lè)觀鎖更新功能的示例代碼
這篇文章主要介紹了MP(MyBatis-Plus)實(shí)現(xiàn)樂(lè)觀鎖更新功能的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01
詳解springboot啟動(dòng)時(shí)是如何加載配置文件application.yml文件
這篇文章主要介紹了詳解springboot啟動(dòng)時(shí)是如何加載配置文件application.yml文件,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-06-06

