CodeGym /课程 /JAVA 25 SELF /泛型通配符

泛型通配符

JAVA 25 SELF
第 27 级 , 课程 4
可用

1. 泛型的不变性:List<Number> ≠ List<Integer>

在 Java 中,泛型是严格的:它们是不变的。这意味着即使一个类型是另一个类型的子类型(例如,IntegerNumber 的子类型),使用这些类型的集合彼此之间也没有关系。

想象一下:你有一个装苹果的盒子(List<Integer>),而有人说它其实是“装水果的盒子”(List<Number>)。看起来好像有道理,因为苹果是水果。但这样就可以往这个盒子里放香蕉(Double),一切就会被破坏——这个盒子实际上是“只装苹果”的。

代码示例:

List<Integer> intList = new ArrayList<>();
List<Number> numList = intList; // 编译错误!

编译器在这里特意保持严格:它不允许把“苹果”的列表变成“水果”的列表。否则我们就可以往里面添加任何东西,程序会在运行时崩溃。

因此,List<Number>List<Integer> 是两个完全不同的类型,尽管 Integer 本身是 Number 的子类型。

与数组不同:

Integer[] intArr = new Integer[10];
Number[] numArr = intArr; // 允许(数组是协变的)
numArr[0] = 3.14; // 运行时错误 (ArrayStoreException)

结论:泛型是不变的,以确保在编译期的类型安全。

2. 类型参数的边界

有时需要限制泛型类或方法可以处理哪些类型。为此使用边界(bounds)

上界(extends

class Stats<T extends Number> {
    private T[] nums;
    // ...
}

现在 Stats 只能与 Number 的子类型一起工作(IntegerDoubleFloat 等)。尝试创建 Stats<String> 会导致编译错误。

与接口的约束

class Sorter<T extends Comparable<T>> {
    void sort(List<T> list) { /* ... */ }
}

现在 Sorter 只能与实现了 Comparable 接口的类型一起工作。

多重边界

可以通过 & 同时指定多个约束:

class MyClass<T extends Number & Comparable<T>> { /* ... */ }

T 必须是 Number 的子类,并且实现 Comparable<T>

顺序很重要:先写类,再写接口。

3. 认识通配符:?

通配符(Wildcard)是一种“占位”的类型,表示:“这里可以是某个类型,但我不知道具体是哪一种。”

示例:

  • List<?> —— 任意类型的列表。
  • List<? extends Number> —— 任何 Number 子类型(如 IntegerDouble)的列表。
  • List<? super Integer> —— 任何 Integer 的父类型(如 IntegerNumberObject)的列表。

为什么需要通配符?

它们允许编写能与不同但相关类型的集合一起工作的泛型方法。

示例:只读(producer)

void printNumbers(List<? extends Number> list) {
    for (Number n : list) {
        System.out.println(n);
    }
    // list.add(123); // 错误!不能添加元素
}
  • 可以将元素作为 Number 来读取。
  • 不能添加元素(除了 null)。

示例:只写(consumer)

void addIntegers(List<? super Integer> list) {
    list.add(42); // OK
    // Integer x = list.get(0); // 错误!我们不知道会返回什么类型
}
  • 可以添加 Integer(或其子类型)的元素。
  • 不能安全地将元素作为 Integer 读取(只能当作 Object 读取)。

PECS 规则

PECS —— “Producer Extends, Consumer Super”:

  • Producer — Extends:如果集合只提供数据(producer),使用 ? extends T
  • Consumer — Super:如果集合只接收数据(consumer),使用 ? super T

好记法:extends —— 用于读取(producer),super —— 用于写入(consumer)。

对比:数组是协变的,泛型是不变的

  • 数组是协变的:可以把 Integer[] 赋值给 Number[] 类型的变量。
  • 泛型是不变的:不能把 List<Integer> 赋值给 List<Number> 类型的变量。

通配符可以在一定程度上“缓解”泛型的不变性。

5. 泛型方法与类型推断;限制(类型擦除)

泛型方法

可以声明与任意类型一起工作的泛型方法:

public static <T> void printList(List<T> list) {
    for (T elem : list) {
        System.out.println(elem);
    }
}

类型推断(type inference)

Java 可以“推断”类型参数:

List<String> strings = List.of("a", "b");
printList(strings); // T = String

泛型的限制:类型擦除

  • 在 Java 中,泛型通过类型擦除实现:编译后会删除类型参数的信息。
  • 在字节码中,List<String>List<Integer> 没有差别。
  • 不能创建泛型类型的数组:new List<String>[10] —— 错误。
  • 不能对带类型参数的实例使用 instanceofobj instanceof List<String> —— 错误。

6. 集合与 Stream API 实战

在列表之间复制元素

public static <T> void copy(List<? super T> dest, List<? extends T> src) {
    for (T item : src) {
        dest.add(item);
    }
}
  • src —— producer(? extends T
  • dest —— consumer(? super T

用法:

List<Integer> ints = List.of(1, 2, 3);
List<Number> nums = new ArrayList<>();
copy(nums, ints); // OK: Number 是 Integer 的超类型

Stream API 与通配符

List<Integer> ints = List.of(1, 2, 3);
List<? extends Number> numbers = ints;

numbers.stream()
    .map(Number::doubleValue)
    .forEach(System.out::println);

使用通配符的打印

public static void printAll(List<?> list) {
    for (Object o : list) {
        System.out.println(o);
    }
}

7. 常见错误

错误 №1:Raw types(原始类型)。
使用“原始”类型会关闭类型检查,并导致运行时错误。

List list = new ArrayList(); // raw type — 不好!
list.add("String");
list.add(123); // 可以添加任何东西

String s = (String) list.get(1); // ClassCastException!

不要使用 raw types。务必声明类型参数:List<String>List<Integer>

错误 №2:带有 extends 的不安全操作。
尝试向带有 ? extends ... 的集合添加元素会导致编译错误。

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

错误 №3:类型擦除导致的重载冲突。
不能仅根据泛型参数来重载方法——类型擦除后签名会相同。

public void process(List<String> list) { /* ... */ }
public void process(List<Integer> list) { /* ... */ } // 编译错误!

错误 №4:泛型数组。
由于类型擦除,不能创建参数化类型的数组。

List<String>[] arr = new List<String>[10]; // 编译错误!
1
调查/小测验
集合的接口第 27 级,课程 4
不可用
集合的接口
集合的接口
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION