Java泛型是JDK5中引入的一个新特性,泛型提供了编译是类型安全检测机制,这个机制允许开发者在编译是检测非法类型。泛型的本质就是参数化类型,就是在编译时对输入的参数指定一个数据类型。
泛型带来的好处:
1.类型安全:编译是检查类型是否匹配,避免了ClassCastexception的发生。
2.消除代码强制类型转换:减少了一些类型转换操作。
3.代码复用:可以支持多种数据类型,不要重复编写代码,例如:我们常用的统一响应结果类。
4.增强可读性:通过类型参数就直接能看出要填入什么类型。
第1分钟:为什么需要通配符?
假设你有一个方法要打印所有类型的List:
java
// 只能打印List<String>
void print(List<String> list) { ... }
// 想要打印 List<String>, List<Integer>, List<Fruit>...
通配符就是解决"我需要一个能装任何类型的List"这个问题。
第2分钟:三种通配符,记住这个表格
| 通配符 | 通俗叫法 | 你能做什么 | 你不能做什么 |
|---|---|---|---|
List<?> |
无界通配符 | 只读 ,只能当Object用 |
什么都加不了 (除了null) |
List<? extends Apple> |
上界通配符 | 只读 ,拿出来就是Apple |
什么都加不了 (除了null) |
List<? super Apple> |
下界通配符 | 只写 ,能加Apple和它的子类 |
读出来只能当Object用 |
"上界"和"下界"?
想象类型层次图 ,越往上越抽象(Object在顶端):
java
Object (顶层)
↑
Fruit
↑ ↑
Apple Orange
↑
RedApple
第3分钟:三个核心规则(背下来!)
规则1: ? extends ------ 只能读,不能写
List<? extends T> 是生产者,因为它"吐"数据
java
List<? extends Apple> basket = new ArrayList<RedApple>();
Apple a = basket.get(0); // ✅ 拿出来一定是Apple
basket.add(new Apple()); // ❌ 编译错误!
// basket可能是List<RedApple>,你加Apple进去不安全
记住 :extends = 往外取(extends → extract)
规则2: ? super ------ 只能写,不能读(精确读取)
List<? super T> 是消费者,因为它"吃"数据
java
List<? super Apple> basket = new ArrayList<Fruit>();
basket.add(new Apple()); // ✅ 安全,Apple是Fruit的子类
basket.add(new RedApple()); // ✅ 安全,RedApple是Apple的子类
Apple a = basket.get(0); // ❌ 编译错误!
// basket可能是List<Object>,拿出来可能是任何东西
记住 :super = 往里放(super → supply)
List<? super Apple>可能是 List<Apple> 或者 List<Fruit> List<Object>。因为不确定,所以我往里面放元素时候,只能放Apple 或者子类RedApple。如果我往里面放Fruit元素,可能它实际是List这个时候就有问题
关键点 :因为不确定 它到底是哪种,编译器必须保证所有可能的情况都类型安全。
为什么能放 Apple 或 RedApple(子类)?
- 如果实际是
List<Apple>:直接匹配 - 如果实际是
List<Fruit>:Apple是Fruit的子类,可以向上转型安全放入 - 如果实际是
List<Object>:任何对象都可以放入
无论哪种情况,放 Apple 或 RedApple 都100%安全。
总结:? super Apple 的消费者视角,就是------我只接收 Apple 家族的食物,其他一律不行。
规则3: <?> ------ 只能读,且只能当Object用
List<?> 是 List<? extends Object> 的语法糖!
java
List<?> basket = new ArrayList<String>();
Object o = basket.get(0); // ✅ 只能当Object
basket.add("hello"); // ❌ 编译错误!
// 编译器完全不知道?是什么,什么都不让加
记住 :? = 彻底只读
第4分钟:什么时候用哪个?(PECS原则)
-
P roducer Extends(生产者用extends)
-
你的方法从List里拿数据用 →
? extends T -
示例:遍历、读取、计算
-
C onsumer Super(消费者用super)
- 你的方法往List里放数据 →
? super T - 示例:填充、添加、写入
- 你的方法往List里放数据 →
快速判断 :看方法里get()多还是add()多?
第5分钟:新手最容易踩的坑
java
// ❌ 错误:想同时读写
void badMethod(List<? extends Apple> list) {
list.add(list.get(0)); // 编译错误!
}
// ✅ 正确:根据需求选择
void readMethod(List<? extends Apple> list) { /* 只读 */ }
void writeMethod(List<? super Apple> list) { /* 只写 */ }
终极口诀:
上界Stream流,下界Collect收
课后作业(5秒钟自测)
这个方法该用什么通配符?
java
void copy(List<___> src, List<___> dest) {
dest.add(src.get(0)); // 从src读,往dest写
}
答案:src用? extends T,dest用? super T。这就是Collections.copy()的签名!
恭喜你,入门了! 记住那个表格和PECS,剩下的就是多写代码。