134. Java 泛型 - 上限通配符

67 阅读3分钟

134. Java 泛型 - 上限通配符 (? extends T)

在 Java 泛型中,我们可以使用上限通配符 (? extends T) 来放宽对变量的限制,使方法能够适用于某个类型及其所有子类型。

让我们深入了解它的概念,并通过示例加深理解。


1. 什么是上限通配符 (? extends T)?

在泛型中,我们可以使用 ? extends T 来表示“某个类 T 及其所有子类”。

  • 其中,extends 在这里不仅表示类的继承,也可以用于接口的实现
  • 这在处理同一类层级中的不同泛型类型时非常有用。

语法

List<? extends Number> list;
  • list 可以接受 List<Number>List<Integer>List<Double>List<Float> 等。
  • 但是,不能向 list 中添加元素(除了 null),因为 Java 无法确定具体的子类型。

2. 为什么使用 ? extends T

假设我们需要编写一个方法,它能够接受 List<Integer>List<Double>List<Number>,而不需要为每个具体类型单独编写方法。

使用 ? extends T 可以让方法适用于T 及其子类,避免代码重复,增强通用性。


3. 示例:使用 ? extends T 进行数据读取

📌 需求:编写一个方法计算 List<Number> 及其子类(如 IntegerDouble)的总和。

✅ 示例 1:计算数值列表的总和

import java.util.Arrays;
import java.util.List;

public class UpperBoundWildcard {
    public static double sumOfList(List<? extends Number> list) {
        double sum = 0.0;
        for (Number n : list) {
            sum += n.doubleValue(); // ✅ 可以安全地调用 Number 的方法
        }
        return sum;
    }

    public static void main(String[] args) {
        List<Integer> integers = Arrays.asList(1, 2, 3);
        System.out.println("Sum of integers: " + sumOfList(integers)); // 输出: 6.0

        List<Double> doubles = Arrays.asList(1.2, 2.3, 3.5);
        System.out.println("Sum of doubles: " + sumOfList(doubles)); // 输出: 7.0
    }
}

🔍 解析

  • sumOfList(List<? extends Number> list) 接受 Number 及其所有子类
  • list 可以是 List<Integer>List<Double>List<Float> 等。
  • n.doubleValue()安全调用 Number 类的方法,因为所有 Number 的子类都继承了 doubleValue()

4. 注意事项:为什么不能向 List<? extends T> 添加元素?

List<? extends T> 中,我们无法添加元素(除了 null),否则会导致编译错误。

❌ 错误示例

List<? extends Number> list = new ArrayList<Integer>();
list.add(5);  // ❌ 编译错误
list.add(3.14); // ❌ 编译错误

🔍 为什么?

  • list 可能是 List<Integer>,但 list.add(3.14) 试图添加 Double,这会破坏 List<Integer> 的类型安全性。
  • Java 编译器无法确保 list 的实际类型,因此不允许添加元素,但可以安全读取元素(因为所有元素都是 Number)。

✅ 解决方案

List<Number> list = new ArrayList<>();
list.add(5);      // ✅ 允许
list.add(3.14);   // ✅ 允许

如果需要向列表添加元素,就不能使用 ? extends T,而应该使用具体类型 T


5. 适用场景

适用于只“读取”数据的情况(Producer)。 ✅ 适用于同一继承层级中的多个泛型类型(如 Number 的子类)。 ✅ 适用于通用方法,使代码更具灵活性


6. 结论

? extends T 表示 T 及其所有子类,适用于“读取”数据的情况。 ✅ 无法向 List<? extends T> 添加元素,因为 Java 无法确保其实际类型。 ✅ 使用 ? extends T 可以增强代码的通用性,避免代码重复。 ✅ 典型用法:计算数值列表的总和,遍历对象并调用父类方法


🎯 记住口诀:

  • Producer Extends (? extends T) 👉 用于“读取”数据
  • Consumer Super (? super T) 👉 用于“写入”数据

🚀 泛型让 Java 代码更灵活,学会 ? extends T,让你的代码更通用!