当前位置: 首页 > news >正文

Java笔记 —— 泛型

在Java开发中,泛型(Generics)是一项革命性的特性,它于JDK 5引入,让代码在编译时就能捕获类型错误,并消除了大量繁琐的类型转换。本文将带你全面了解Java泛型的原理、使用场景和最佳实践。

一、为什么需要泛型?

在没有泛型的年代,我们只能这样写集合:

List list = new ArrayList(); list.add("hello"); list.add(123); // 随意添加不同类型 String s = (String) list.get(0); // 需要强制转换,容易出错

这种方式存在两个问题:

  1. 类型不安全:可以向集合中添加任意类型,编译时无法检查。

  2. 代码冗长:取值时需要进行强制类型转换,而且转换失败会在运行时抛出ClassCastException

泛型解决了这两个问题,它提供了编译时类型安全消除了强制转换

List<String> list = new ArrayList<>(); list.add("hello"); // list.add(123); // 编译错误,类型不匹配 String s = list.get(0); // 无需转换

二、泛型的基本概念

2.1 类型参数

泛型中的类型参数通常用单个大写字母表示,常见的有:

  • E- Element(元素,常用于集合)

  • K- Key(键)

  • V- Value(值)

  • N- Number(数字)

  • T- Type(类型)

  • S,U,V- 第二、三、四个类型参数

2.2 泛型类

定义一个泛型类,在类名后加上尖括号<>

public class Box<T> { private T content; public void set(T content) { this.content = content; } public T get() { return content; } }

使用:

Box<String> stringBox = new Box<>(); stringBox.set("Hello"); String str = stringBox.get(); Box<Integer> intBox = new Box<>(); intBox.set(123); Integer num = intBox.get();

2.3 泛型接口

接口也可以声明类型参数:

public interface Pair<K, V> { K getKey(); V getValue(); }

实现时指定具体类型:

public class OrderedPair<K, V> implements Pair<K, V> { private K key; private V value; public OrderedPair(K key, V value) { this.key = key; this.value = value; } @Override public K getKey() { return key; } @Override public V getValue() { return value; } }

2.4 泛型方法

方法也可以声明自己的类型参数,独立于类:

public class Util { // 泛型方法,<T>放在返回值之前 public static <T> T getMiddle(T... a) { return a[a.length / 2]; } // 更复杂的例子:比较两个Pair public static <K, V> boolean compare(Pair<K, V> p1, Pair<K, V> p2) { return p1.getKey().equals(p2.getKey()) && p1.getValue().equals(p2.getValue()); } }

调用时,类型参数会被自动推断:

String middle = Util.getMiddle("a", "b", "c"); Integer midNum = Util.getMiddle(1, 2, 3);

三、类型边界

有时我们需要限制类型参数的范围,例如要求类型参数必须是某个类的子类或实现某个接口。

3.1 上界通配符

使用extends关键字设置上界:

// 只接受Number及其子类 public class NumberBox<T extends Number> { private T number; public double doubleValue() { return number.doubleValue(); } }

可以指定多个边界,用&连接:

public class MultipleBound<T extends Number & Comparable<T>> { // T必须同时是Number的子类并实现Comparable接口 }

3.2 泛型方法中的类型边界

// 计算数组中的最小值,T必须实现Comparable public static <T extends Comparable<T>> T min(T[] array) { if (array == null || array.length == 0) return null; T smallest = array[0]; for (int i = 1; i < array.length; i++) { if (smallest.compareTo(array[i]) > 0) { smallest = array[i]; } } return smallest; }

四、通配符(Wildcard)

通配符?表示未知类型,常用于方法的参数中,使方法更灵活。

4.1 无界通配符

public void printList(List<?> list) { for (Object obj : list) { System.out.println(obj); } }

List<?>表示任何类型的List,但不能向其中添加元素(除了null),因为类型未知。

4.2 上界通配符

// 接受任何Number及其子类的List public double sum(List<? extends Number> list) { double sum = 0.0; for (Number num : list) { sum += num.doubleValue(); } return sum; }

? extends T表示T或T的子类。这种形式适用于读取场景,因为你知道元素至少是T类型。

4.3 下界通配符

// 向列表中添加Integer或其父类对象 public void addNumbers(List<? super Integer> list) { for (int i = 1; i <= 10; i++) { list.add(i); // 可以添加Integer } // Object obj = list.get(0); // 可以获取,但类型是Object }

? super T表示T或T的父类。这种形式适用于写入场景,因为你知道可以安全地添加T类型及其子类型。

4.4 PECS原则

PECS(Producer Extends, Consumer Super)是通配符使用的经典原则:

  • Producer Extends:如果参数化类型代表一个生产者(提供数据),使用<? extends T>

  • Consumer Super:如果代表一个消费者(消费数据),使用<? super T>

// 生产者:从集合中读取数据 public void copy(List<? extends Number> src, List<? super Number> dest) { for (Number num : src) { dest.add(num); } }

五、类型擦除(Type Erasure)

Java泛型是通过类型擦除实现的,这意味着泛型信息只在编译时存在,运行时会被移除。

5.1 什么是类型擦除?

编译器在编译时进行类型检查,然后将泛型类型替换为原始类型(Raw Type),并插入必要的强制转换。

例如:

List<String> list = new ArrayList<>(); list.add("hello"); String s = list.get(0);

编译后实际等价于:

List list = new ArrayList(); list.add("hello"); String s = (String) list.get(0);

5.2 擦除的规则

  • 无限定类型参数(如<T>)被替换为Object

  • 有上界的类型参数(如<T extends Number>)被替换为第一个边界类型(这里是Number)。

  • 泛型方法也会被擦除,并可能生成桥方法(Bridge Method)以保持多态。

5.3 擦除带来的限制

由于擦除,泛型有一些使用限制:

1. 不能实例化类型参数

// 错误 T obj = new T(); // 错误 T[] array = new T[10];

2. 静态上下文中不能引用类型参数

public class Box<T> { // 错误:静态成员不能使用类的类型参数 private static T value; // 错误:静态方法不能使用类的类型参数 public static T getValue() { ... } }

3. 不能创建泛型数组

// 编译错误 List<String>[] array = new List<String>[10]; // 可以这样绕过,但不安全 List<String>[] array = (List<String>[]) new List[10];

4. 不能使用instanceof判断泛型类型

// 错误 if (obj instanceof List<String>) { ... } // 只能判断原始类型 if (obj instanceof List<?>) { ... }

六、泛型与继承

泛型类型与继承的关系需要特别注意:

  • List<String>不是List<Object>的子类型。

  • List<Integer>不是List<Number>的子类型,尽管IntegerNumber的子类型。

  • 数组是协变的(covariant),而泛型是不变的(invariant)。

// 数组协变:允许 Number[] numbers = new Integer[10]; // 泛型不变:不允许 List<Number> list = new ArrayList<Integer>(); // 编译错误

这正是通配符发挥作用的地方:

// 使用上界通配符实现类似协变 List<? extends Number> list = new ArrayList<Integer>();

七、泛型的最佳实践

7.1 优先使用泛型,避免原始类型

永远不要使用原始类型(如List而不是List<String>),这会失去类型安全性。

7.2 使用通配符增加API灵活性

在设计方法参数时,合理使用? extends? super让API更通用。

7.3 优先考虑使用泛型方法而不是通配符作为返回值

// 好的做法:使用泛型方法 public static <T> T getFirst(List<T> list) { ... } // 不推荐:通配符作为返回值,调用者需要强制转换 public static <?> getFirst(List<?> list) { ... }

7.4 类型参数命名要有意义

对于简单情况,单字母可以接受;但复杂场景下,使用有意义的名称(如KeyTypeValueType)提高可读性。

7.5 避免泛型数组

尽量使用集合代替数组,或者使用List来达到类似目的。

八、Java 7+的改进

  • 菱形操作符:Java 7开始,实例化泛型类时可以省略类型参数:

    List<String> list = new ArrayList<>(); // 菱形操作符
  • 局部变量类型推断:Java 10引入var,可以简化局部变量声明:

    var list = new ArrayList<String>(); // 类型推断为ArrayList<String>

九、总结

Java泛型是一个强大且复杂的特性,它通过编译时类型检查提供了类型安全,并消除了强制转换的繁琐。理解泛型需要掌握:

  • 泛型类、接口、方法的定义与使用

  • 类型边界的约束

  • 通配符的灵活运用及PECS原则

  • 类型擦除的原理及其带来的限制

http://www.jsqmd.com/news/518778/

相关文章:

  • ABAQUS纤维复合材料热固化仿真:子粘弹性模型与内附CAE文件
  • 三电平逆变器实战:从SVPWM调制到中点平衡的硬核玩法
  • 从‘靶场‘到‘实战‘:把Pikachu漏洞环境搬上云服务器(阿里云/腾讯云实操)
  • 基于A*算法的往返式全覆盖路径规划的改进算法及MATLAB实现代码
  • 这个十行代码的观测器 凭什么让电机控制工程师直呼真香
  • Gemini 3 Pro技术深度拆解:原生多模态与MoE架构解析
  • 深入PX4 DataValidator:看懂传感器‘信任度’confidence是如何算出来的
  • 西门子828D数控系统及PLC全功能调试流程包:涵盖车床、加工中心、攻钻机、铣床设备
  • 【第三周】论文精读:Scaling Knowledge Graph Construction through Synthetic Data Generation and Distillation
  • 服务器带外管理实战:手把手教你用IPMI远程控制Dell iDRAC(附常见问题排查)
  • 2026年Gemini 3.1 Pro技术深度拆解:推理能力翻倍与国内直访方案
  • 多模态数据标注实战指南:5大高效工具与避坑策略(附Label Studio模板)
  • 半主动悬架搞起来比想象中有意思。最近在玩天棚阻尼控制的1/4车模型,这玩意儿对车身垂向加速度的控制效果确实有点东西。咱们直接上干货,先说说模型怎么搭的
  • 免费馅饼(dp模版1的变体
  • 基于CODESYS的ModbusTCP通信实战:从配置到FPGA集成
  • Java笔记 —— 值传递与“引用传递”
  • CPEditor刷题环境配置避坑指南:解决C++14语法不兼容问题
  • 02nginx配置文件
  • java毕业设计基于springboot音乐分享交流平台project60526
  • 895. 最长上升子序列(线性dp模版题2
  • Java笔记——数据类型(为什么商业计算必须用BigDecimal?)
  • Java笔记——包装类(自动拆装箱)
  • FatMouse‘s Speed(dp模版2 最长上升子序列
  • Python+PySpark+Hadoop图书推荐系统 图书可视化大屏 网上 图书个性化推荐系统 Django框架 可视化 协同过滤推荐算法
  • 金融级容灾标准:TDengine时序数据库实现分钟级RTO与秒级RPO的架构解析
  • 16 openclaw与数据库集成:ORM使用与性能优化
  • 基于vue的民族婚纱预订系统[vue]-计算机毕业设计源码+LW文档
  • 1010. 拦截导弹(dp模版二 最长上升子序列
  • 17 openclaw数据库连接池配置:避免性能瓶颈的关键
  • 好写作AI | 艺术类毕业创作说明文中AI辅助感性表达与理性论证的平衡