泛型中的上界限定符`extends`和下界限定符`super`各自的作用是什么,它们之间有何不同?

参考回答**

在 Java 的泛型中,extendssuper 是泛型通配符的 上界限定符下界限定符,它们的作用是限制泛型参数的类型范围,从而在方法或类中对泛型参数进行更加灵活的操作。

  • ? extends T:上界通配符,表示泛型类型可以是 T或 T的子类(包括 T本身)。
    • 主要用于只读操作。
  • ? super T:下界通配符,表示泛型类型可以是 T或 T的父类(包括 T本身)。

  • 主要用于写入操作。

它们的区别在于:

  • ? extends T:适用于协变(读取时可以保证是某种类型及其子类型)。
  • ? super T:适用于逆变(写入时可以保证是某种类型及其父类型)。

详细讲解与拓展

1. ? extends T 上界通配符

作用:限制泛型参数必须是类型 TT 的子类。

  • 常用于只读场景,因为可以安全地读取为 T 类型。
  • 不允许往泛型容器中添加 T 或其子类的元素(除了 null),因为类型不确定。

示例:计算总和 假设有一个方法需要接受一个存储数字的泛型列表并计算总和:

public double sum(List<? extends Number> list) {
    double sum = 0;
    for (Number num : list) {
        sum += num.doubleValue(); // 可以安全读取为 Number
    }
    return sum;
}

使用场景

  • 允许传入 List<Integer>List<Double>Number 的子类列表。
  • 限制:不能往 list 中添加元素,因为编译器无法确定确切的子类型。
List<Integer> intList = List.of(1, 2, 3);
List<Double> doubleList = List.of(1.1, 2.2, 3.3);

System.out.println(sum(intList));     // 输出 6.0
System.out.println(sum(doubleList));  // 输出 6.6

注意:在使用 ? extends T 时,可以从列表中读取元素,但无法向列表中添加元素(除了 null)。


2. ? super T 下界通配符

作用:限制泛型参数必须是类型 TT 的父类。

  • 常用于写入场景,因为可以安全地向泛型容器中添加 T 类型或其子类型的元素。
  • 不保证读取的类型,因为泛型参数可能是 T 的父类,读取时只能保证是 Object 类型。

示例:添加元素 假设有一个方法需要向泛型列表中添加 Integer 或其子类的值:

public void addIntegers(List<? super Integer> list) {
    list.add(1);
    list.add(2);
    list.add(3);
}

使用场景

  • 允许传入 List<Integer>List<Number>List<Object>
  • 限制:从列表中读取元素时,编译器只能保证它是 Object 类型。
List<Number> numList = new ArrayList<>();
addIntegers(numList);

System.out.println(numList); // 输出 [1, 2, 3]

注意:在使用 ? super T 时,可以向列表中写入 TT 的子类型的元素,但从列表中读取元素时,只能保证读取到 Object


3. ? extends T? super T 的对比

特性 ? extends T ? super T
适用场景 读取:从列表中读取数据,保证是 T 或其子类 写入:向列表中添加数据,保证是 T 或其子类
类型范围 TT 的子类 TT 的父类
读取操作 可以读取为 T 只能读取为 Object
写入操作 不能写入(除了 null 可以写入 T 或其子类
典型用途 协变:传入子类时适合只读场景 逆变:传入父类时适合只写场景

示例代码对比:

// 使用 ? extends T
public void processList(List<? extends Number> list) {
    Number num = list.get(0); // 可以读取为 Number
    // list.add(1); // 编译错误:无法确定可以添加的具体类型
}

// 使用 ? super T
public void processList(List<? super Integer> list) {
    list.add(1); // 可以安全写入 Integer 类型
    Object obj = list.get(0); // 读取时只能作为 Object
}

4. 常见应用场景

(1) 上界通配符:? extends T

  • 读取数据:确保从集合中读取的元素至少是 T 类型。
  • 典型应用:处理需要从泛型集合中读取数据的逻辑,如汇总计算、排序等。

示例:打印列表内容

public void printList(List<? extends Number> list) {
    for (Number num : list) {
        System.out.println(num);
    }
}

(2) 下界通配符:? super T

  • 写入数据:确保向集合中写入的元素是 T 类型或其子类。
  • 典型应用:处理需要向泛型集合中添加数据的逻辑,如填充数据、初始化集合等。

示例:向列表中添加数据

public void fillList(List<? super Integer> list) {
    list.add(1);
    list.add(2);
    list.add(3);
}

5. PECS 原则

Java 泛型设计中的一个经典原则是 PECS(Producer Extends, Consumer Super):

  • Producer Extends:如果你需要从集合中获取数据,使用 ? extends T
  • Consumer Super:如果你需要向集合中添加数据,使用 ? super T

示例:

  • 使用 ? extends T:适合数据的生产者,如 List<? extends Number>
  • 使用 ? super T:适合数据的消费者,如 List<? super Integer>

6. 总结

  • ? extends T(上界通配符)
    • 泛型类型可以是 T 或其子类。
    • 用途:适用于只读场景。
    • 限制:不允许写入元素(除了 null)。
  • ? super T(下界通配符)
    • 泛型类型可以是 T 或其父类。
    • 用途:适用于只写场景。
    • 限制:读取时只能作为 Object

发表评论

后才能评论