Java泛型通配符入门教程

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这个时候就有问题

关键点 :因为不确定 它到底是哪种,编译器必须保证所有可能的情况都类型安全

为什么能放 AppleRedApple(子类)

  • 如果实际是 List<Apple>:直接匹配
  • 如果实际是 List<Fruit>AppleFruit 的子类,可以向上转型安全放入
  • 如果实际是 List<Object>:任何对象都可以放入

无论哪种情况,放 AppleRedApple 都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
    • 示例:填充、添加、写入

快速判断 :看方法里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 Tdest? super T。这就是Collections.copy()的签名!


恭喜你,入门了! 记住那个表格和PECS,剩下的就是多写代码。

相关推荐
侠客行03171 天前
Mybatis连接池实现及池化模式
java·mybatis·源码阅读
蛇皮划水怪1 天前
深入浅出LangChain4J
java·langchain·llm
老毛肚1 天前
MyBatis体系结构与工作原理 上篇
java·mybatis
风流倜傥唐伯虎1 天前
Spring Boot Jar包生产级启停脚本
java·运维·spring boot
Yvonne爱编码1 天前
JAVA数据结构 DAY6-栈和队列
java·开发语言·数据结构·python
Re.不晚1 天前
JAVA进阶之路——无奖问答挑战1
java·开发语言
你这个代码我看不懂1 天前
@ConditionalOnProperty不直接使用松绑定规则
java·开发语言
fuquxiaoguang1 天前
深入浅出:使用MDC构建SpringBoot全链路请求追踪系统
java·spring boot·后端·调用链分析
琹箐1 天前
最大堆和最小堆 实现思路
java·开发语言·算法
__WanG1 天前
JavaTuples 库分析
java