发布于2026-07-07 阅读(0)
扫一扫,手机访问
不久前,有朋友问到Ja va泛型中的通配符T、E、K、V、?到底是什么,又有什么实际用处。这个问题瞬间把人拉回刚学Ja va那会儿——说实话,刚开始接触泛型时,这些符号看着就让人发懵,不真正用起来很难融会贯通。后来是在项目实战中,频繁看到别人的代码、翻看源码时总碰到它们,才特意花了些功夫研究清楚。今天就把这些“老朋友”掰开揉碎讲一讲。

在具体介绍每个通配符之前,有必要先回顾一下泛型本身。Ja va泛型是JDK 5引入的核心特性,提供了一套编译时类型安全检测机制,允许开发者在编译阶段就堵住非法类型的隐患。说白了,泛型就是“参数化类型”——你在写代码时能提前指定将来要操作的数据类型,编译器帮你把关。
ClassCastException。 // 非泛型写法(存在类型转换风险)
List list1 = new ArrayList();
list1.add("a");
Integer num = (Long) list1.get(0); // 运行时抛出 ClassCastException
// 泛型写法(编译时检查类型)
List list2 = new ArrayList<>();
// list.add(1); // 编译报错
list2.add("a");
String str = list2.get(0); // 无需强制转换
(User)之类的强转。// 非泛型写法
Map map1 = new HashMap();
map1.put("user", new User());
User user1 = (User) map1.get("user");
// 泛型写法
Map map2 = new HashMap<>();
map2.put("user", new User());
// 自动转换
User user2 = map2.get("user");
@Data @NoArgsConstructor @AllArgsConstructor public class Result{ /** * 响应状态码 */ private int code; /** * 响应信息 */ private String message; /** * 响应数据 */ private T data; /** * 时间戳 */ private long timestamp; 其他代码省略...
Listlist = new ArrayList<>();
实际写代码时,我们常会遇到T、E、K、V、?这些符号。它们经常出现在各种框架源码和业务工具类中。那么,它们究竟扮演什么角色?彼此又有什么不同?接下来逐一拆解。
pubile class A{ prvate T t; //其他省略... } //创建一个不带泛型参数的A A a = new A(); a.set(new B()); B b = (B) a.get();//需要进行强制类型转换 //创建一个带泛型参数的A A a = new A(); a.set(new B()); B b = a.get();
List。Listlist = new ArrayList<>();
Mapmap = new HashMap<>();
Mapmap = new HashMap<>();
需要说明的是,这些只是开发中的命名约定,并非强制规则。但遵循这些惯例能让代码更易读,团队成员一看便知意图。
问号通配符比前面几个稍微复杂一些,它有三种变体:无界、上界、下界。
> —— 表示未知类型,可以接收任意类型的集合,通常用在只读场景。 // 使用无界通配符处理任意类型的查询结果
public void logQueryResult(List> resultList) {
resultList.forEach(obj -> log.info("Result: {}", obj));
}
extends T> —— 表示类型是T或T的子类,适合“生产者”场景(只往外读,不往里写)。// 使用上界通配符读取缓存 publicT getCache(String key, Class clazz) { Object value = redisTemplate.opsForValue().get(key); return clazz.cast(value); }
super T> —— 表示类型是T或T的父类,适合“消费者”场景(往里写数据,读出来只能当Object处理)。 // 使用下界通配符写入缓存
public void setCache(String key, super Serializable> value) {
redisTemplate.opsForValue().set(key, value);
}
下面是一个综合示例,把三种通配符放一起演示,能更直观地理解各自的行为差异:
import ja va.util.ArrayList;
import ja va.util.List;
public class Demo {
//实体类
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");
}
}
/**
* 无界通配符 >
*/
// 只能读取元素,不能写入(除null外)
public static void printAllElements(List> list) {
for (Object obj : list) {
System.out.println(obj);
}
// list.add("test"); // 编译错误!无法写入具体类型
list.add(null); // 唯一允许的写入操作
}
/**
* 上界通配符 extends T>
*/
// 安全读取为Animal,但不能写入(生产者场景)
public static void processAnimals(List extends Animal> animals) {
for (Animal animal : animals) {
animal.eat();
}
// animals.add(new Dog()); // 编译错误!无法确定具体子类型
}
/**
* 下界通配符 super T>
*/
// 安全写入Dog,读取需要强制转换(消费者场景)
public static void addDogs(List super Dog> dogList) {
dogList.add(new Dog());
dogList.add(new Husky()); // Husky是Dog子类
// dogList.add(new Animal()); // 编译错误!Animal不是Dog的超类
Object obj = dogList.get(0); // 读取只能为Object
if (obj instanceof Dog) {
Dog dog = (Dog) obj; // 需要显式类型转换
}
}
public static void main(String[] args) {
// 测试无界通配符
List strings = List.of("A", "B", "C");
printAllElements(strings);
List integers = List.of(1, 2, 3);
printAllElements(integers);
// 测试上界通配符
List dogs = new ArrayList<>();
dogs.add(new Dog());
processAnimals(dogs);
List huskies = new ArrayList<>();
huskies.add(new Husky());
processAnimals(huskies);
// 测试下界通配符
List animals = new ArrayList<>();
addDogs(animals);
System.out.println(animals);
List
透过这个示例能很清楚看到:无界通配符只能读不能写(null除外);上界通配符能安全读但不能写;下界通配符能安全写但不能精确读。记住“PECS原则”(Producer Extends, Consumer Super)就基本拿捏了。
很多开发者对泛型的使用停留在“能用就行”的层面,对T、E、K、V、?的真正含义和适用场景往往一知半解。会写代码固然重要,但若能再花点时间深挖这些基础概念,弄清背后的设计意图,不仅能让代码更健壮、可读性更强,也能在实际排查问题时更加游刃有余。知其然,更知其所以然,这才是技术能力持续进阶的关键。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8