《Java 100 天进阶之路》第39篇:Java泛型方法的定义和使用

第39篇:Java泛型方法的定义和使用

📌 系列导航《Java 100 天进阶之路》完整目录 |

⬅️ 上一篇:第38篇:Java是动态语言吗? |

➡️ 下一篇:第40篇:浮点数转成十进制问题


一、核心知识点

  • 泛型类、泛型接口、泛型方法的定义
  • 类型参数:<T><T extends Bound>(有界类型)
  • 类型擦除原理(编译后转为原始类型)
  • 通配符:?? extends T? super T(PECS 原则)
  • 泛型方法和普通方法的区别

二、通俗讲解(1分钟开心学)

1. 为什么需要泛型?

在没有泛型之前,我们从集合中取出的元素都是 Object,需要强制转换,而且类型错误只能在运行时发现。泛型让"类型"成为参数,可以在编译时检查类型安全,代码也更清晰。

2. 泛型方法

泛型方法可以在普通类、泛型类中定义,不要求整个类是泛型。类型参数放在方法修饰符和返回类型之间,如 public static <T> T getMiddle(T... a)

3. 有界类型参数

如果你想限制泛型参数的范围,比如只能接受 Number 的子类,可以写 <T extends Number>。这样 T 就可以调用 Number 的方法(如 doubleValue())。

4. 类型擦除

泛型信息只在编译时存在,运行时会擦除为原始类型(比如 List<String> 变成 List),并插入强制类型转换。这样做是为了兼容旧代码。

5. 通配符

  • ?:表示未知类型,常用于方法参数。
  • ? extends T:表示 T 或 T 的子类(上界),可读不可写(除了 null)。
  • ? super T:表示 T 或 T 的父类(下界),可写不可读(但可读为 Object)。

PECS 原则 :Producer Extends, Consumer Super。如果你要生产 元素(读取),用 extends;如果你要消费 元素(写入),用 super

生活类比

泛型就像一个"万能模具"。你只要告诉模具"今天做苹果派"(指定类型 Integer),它就能产出苹果派,不会给你混进香蕉(类型安全)。类型擦除就像模具工作完后,上面的标签被擦掉了,只剩下"派"这个原始形态。

三、实操代码案例 + 场景说明

场景:写一个通用的数组元素交换方法,以及一个安全地复制列表的方法。

java 复制代码
import java.util.*;

public class GenericMethodDemo {
    // 1. 泛型方法:交换数组中两个位置
    public static <T> void swap(T[] array, int i, int j) {
        T temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }
    
    // 2. 泛型方法:返回两个值中的较大者(有界类型)
    public static <T extends Comparable<T>> T max(T a, T b) {
        return a.compareTo(b) > 0 ? a : b;
    }
    
    // 3. 通配符:打印任意类型的列表
    public static void printList(List<?> list) {
        for (Object obj : list) {
            System.out.print(obj + " ");
        }
        System.out.println();
    }
    
    // 4. 上界通配符:读取数字列表求和(Producer Extends)
    public static double sumList(List<? extends Number> list) {
        double sum = 0.0;
        for (Number num : list) {
            sum += num.doubleValue();
        }
        return sum;
    }
    
    // 5. 下界通配符:向列表添加整数(Consumer Super)
    public static void addIntegers(List<? super Integer> list) {
        for (int i = 1; i <= 5; i++) {
            list.add(i);
        }
    }
    
    public static void main(String[] args) {
        // 交换测试
        String[] fruits = {"apple", "banana", "cherry"};
        swap(fruits, 0, 2);
        System.out.println(Arrays.toString(fruits)); // [cherry, banana, apple]
        
        // 最大值
        System.out.println(max(10, 20));      // 20
        System.out.println(max("cat", "dog")); // dog (词典序)
        
        // 通配符打印
        List<Integer> ints = Arrays.asList(1, 2, 3);
        List<String> strs = Arrays.asList("A", "B");
        printList(ints);
        printList(strs);
        
        // 上界:只能读,不能写
        List<Integer> nums = Arrays.asList(1, 2, 3);
        double sum = sumList(nums);
        System.out.println("Sum: " + sum);
        // sumList(nums).add(4); // 编译错误:不能添加
        
        // 下界:可以写,读只能读为 Object
        List<Object> objects = new ArrayList<>();
        addIntegers(objects);
        System.out.println(objects); // [1,2,3,4,5]
    }
}

四、避坑要点

错误/误区 后果 正确做法
在静态上下文中使用泛型类的类型参数 编译错误 静态方法需要独立声明 <T>
创建泛型数组:new T[10] 编译错误 使用 ArrayList<T>Array.newInstance
泛型方法返回 T 但实参类型推断为 Object 需要强制转换 调用时显式指定类型参数
? extends T 试图添加非 null 元素 编译错误 只读场景使用上界通配符

五、面试高频考点

Q1:什么是类型擦除?

泛型在编译后,类型参数会被替换为它们的边界(如 <T> 变成 Object<T extends Number> 变成 Number),并插入强制类型转换。这是为了向后兼容旧代码。

Q2:List<?>List<Object> 的区别?

List<Object> 可以添加任何对象;List<?> 表示未知类型的列表,只能读(除 null 外不能添加)。前者是具体类型,后者是通配符类型,更安全。

Q3:PECS 原则是什么?

Producer Extends, Consumer Super。如果需要从集合读取元素(Producer),使用 ? extends T;如果需要向集合写入元素(Consumer),使用 ? super T

六、练习题

  1. 代码填空 :编写一个泛型方法 getMiddle,返回任意数组的中间元素(如果长度为偶数,返回中间两个中的第一个)。
  2. 判断List<Integer>List<Number> 的子类型吗?为什么?
  3. 动手 :使用有界类型参数实现一个方法 findMax,找出 List<T extends Comparable<T>> 中的最大值。

📊 你的学习进度

  • 当前:第39篇 / 共44篇 · 第六阶段:NIO、泛型、JVM内幕、字节码(第36~44篇)
  • ✅ 已完成:第1~38篇
  • 📖 正在学:第39篇
  • ⏳ 待学习:第40~44篇

👉 📚 完整目录 & 学习指南 | 🔥 订阅本专栏,不错过每一篇

💡 本专栏每篇都包含:避坑表 + 面试高频考点 + 练习题。每天30分钟,100天拿offer!


👉 下一篇文章预告

《第40篇:浮点数转成十进制问题》

内容简介:浮点数精度丢失原因、0.1+0.2!=0.3、BigDecimal正确使用、金额计算最佳实践。

💡 学完这篇,你将彻底搞懂浮点数陷阱,写出精确的金额计算代码。

📌 《Java 100 天进阶之路 | 从入门到上岗就业》 每天一篇,建议收藏 + 关注 ,一起100天拿offer!

👉 点击关注我,更新后第一时间收到推送!

📌 除了Java,我也在深挖智能物流实战(出版社WMS、托盘调度、机器学习落地)。如果你对技术在不同领域的实战感兴趣,欢迎点击我的头像,看看专栏《出版社物流WMS智能调度实战》。技术相通,思路可鉴。

相关推荐
天天进步20151 小时前
Tunnelto 源码解析 #1:从 tunnelto --port 8000 看内网穿透的完整链路
开发语言
土狗TuGou1 小时前
SQL内功笔记 · 第6篇:窗口函数的使用ROW_NUMBER等
java·数据库·后端·sql·mysql
啄缘之间1 小时前
8.【学习】工业级详细接口约束&覆盖率
开发语言·笔记·学习·uvm·sv
Chase_______1 小时前
【Java基础核心知识点全解·09】Java 内存布局与垃圾回收详解:栈、堆、栈帧、GC Roots 与对象回收
java·开发语言
锋行天下1 小时前
让nginx网关扛下所有攻击
前端·后端·nginx
武子康1 小时前
Java-11 深入浅出 MyBatis 一级缓存详解:从原理到失效场景 Executor
java·后端
寻道码路2 小时前
LangChain4j Java AI 应用开发实战(十):Embedding 模型与文本分类 - 语义向量化
java·人工智能·ai·embedding
江南十四行2 小时前
并发编程(四)
开发语言·python
葱卤山猪2 小时前
C++17 联合体
开发语言·c++