引言
在 Java 中,泛型(Generics)使得我们能够编写类型安全且可重用的代码。为了进一步增强泛型的灵活性,Java 引入了通配符(Wildcard)机制。通配符允许我们在不指定具体类型的情况下,使用 ? extends T? super T 来表示泛型的类型范围,提供更大的灵活性。


一、? extends T上界通配符

? extends T 是上界通配符,表示泛型类型可以是 T 类型或 T 的子类型。通过使用 ? extends T,我们可以表示泛型参数的上界限制,使其只能是 TT 的子类。下面通过示例代码进行详细讲解。

1、? extends T 的使用场景

假设我们有一个 List<? extends Number> 对象,这意味着这个列表的元素可能是 Number 类型或 Number 的子类型(如 IntegerDouble)。使用 ? extends T 时,我们可以从列表中读取元素,但不能向其中添加数据。

代码示例:

import java.util.*;

public class UpperBoundWildcardExample {
    public static void main(String[] args) {
        // List<? extends Number> 可以是 List<Number>、List<Integer> 或 List<Double> 等
        List<? extends Number> numberArray = new ArrayList<Integer>();
        numberArray = new ArrayList<Double>();

        // 由于 numberArray 的元素类型可以是 Number 或其子类,我们可以安全地读取元素
        numberArray.add(10); // 编译错误,因为我们不知道元素的具体类型
        Number number = numberArray.get(0); // 可以安全地从中获取 Number 类型的元素

        System.out.println("Number: " + number);
    }
}

2、为什么不能添加数据?

  • List<? extends Number> 可以包含 Number 或其子类的元素,然而,通配符 ? 使得我们无法确定具体的子类类型。因此,无法确保添加的元素与列表的实际类型匹配。
  • 如果我们尝试往 numberArray 中添加 IntegerDouble,编译器会报错,因为我们无法确定 numberArray 是存储 Integer 类型、Double 类型,还是其他类型的元素。

3、可读取但不可写入

? extends T 通配符可以确保我们从 List 中读取到类型为 T 的数据,或者 T 的子类型。例如,在 List<? extends Number> 中,我们可以安全地读取到 Number 类型的数据,但无法确定具体是 Integer 还是 Double 类型。


二、? super T 下界通配符

? super T 是下界通配符,表示泛型类型可以是 T 类型或 T 的父类型。使用 ? super T 时,我们可以向列表中写入 T 类型及其子类的数据,但无法从列表中读取出具体类型的数据。

1、? super T 的使用场景

假设我们有一个 List<? super Integer> 对象,这意味着该列表可以是 List<Integer>List<Number>List<Object>。我们可以向这个列表中添加 Integer 类型或 Integer 的子类(例如 IntegerLong 等),但从列表中读取时只能读取到 Object 类型的元素。

代码示例:

import java.util.*;

public class LowerBoundWildcardExample {
    public static void main(String[] args) {
        // List<? super Integer> 可以是 List<Integer>、List<Number> 或 List<Object> 等
        List<? super Integer> numberList = new ArrayList<Number>();

        numberList.add(10); // 可以添加 Integer 类型的元素
        numberList.add(20); // 可以继续添加 Integer 类型的元素
        // numberList.add(3.14); // 编译错误,不能添加 Double 类型的元素

        // 由于我们知道 numberList 中的元素至少是 Integer 或它的父类,所以我们只能安全地读取 Object 类型的元素
        Object obj = numberList.get(0); // 读取到的元素是 Object 类型

        System.out.println("Object: " + obj);
    }
}

2、为什么不能读取特定类型的数据?

  • List<? super Integer> 中的元素类型可能是 IntegerInteger 的父类型(如 NumberObject),因此我们只能读取 Object 类型的数据。
  • 从列表中读取元素时,编译器只能保证返回的是 Object 类型,因为我们不能确定元素的具体子类类型。

3、可写入但不可读取

? super T 通配符确保我们可以向列表中添加 T 类型的数据,因为我们知道列表的元素类型是 T 或其父类,能够保证 T 的对象可以添加进去。然而,读取数据时,由于列表的元素类型不确定,编译器只会返回 Object 类型的元素。


三、? super T 和 ? extends T 的比较

1、读取与写入的差异

  • ? extends T(上界通配符):可以从集合中读取 T 或其子类的数据,但不能写入数据。读取的数据类型会被推断为 TT 的子类。
  • ? super T(下界通配符):可以向集合中写入 T 或其子类的数据,但只能从集合中读取 Object 类型的数据,因为无法确定具体的子类型。

2、适用场景

  • ? extends T:适用于你需要从集合中读取数据,但不关心插入数据的场景。
  • ? super T:适用于你需要向集合中添加数据,但不关心从集合中读取数据的场景。

四、PECS 原则

PECS(Producer Extends, Consumer Super)是一个用来记住如何选择通配符的简单规则:

  • Producer Extends:当你需要从集合中读取数据时,使用 ? extends T(生产者:上界通配符)。
  • Consumer Super:当你需要向集合中写入数据时,使用 ? super T(消费者:下界通配符)。

五、实际应用:拷贝数据

在实际开发中,? extends T? super T 通配符常用于数据拷贝等操作。下面是一个拷贝数据的代码示例:

代码示例:
import java.util.*;

public class CopyDataExample {
    public static <T> void copy(List<? super T> dest, List<? extends T> src) {
        for (int i = 0; i < src.size(); i++) {
            dest.set(i, src.get(i)); // 从 src 读取数据并写入 dest
        }
    }

    public static void main(String[] args) {
        List<Integer> src = new ArrayList<>();
        src.add(1);
        src.add(2);

        List<Number> dest = new ArrayList<>();
        copy(dest, src);  // 复制数据

        System.out.println("Dest list: " + dest);
    }
}

在这个例子中,srcList<? extends T>,表示我们可以从中读取 T 类型或其子类的数据。destList<? super T>,表示我们可以向其中写入 T 类型的数据。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐