深入理解 Java 泛型的通配符:? extends T 和 ? super T
目录
引言
在 Java 中,泛型(Generics)使得我们能够编写类型安全且可重用的代码。为了进一步增强泛型的灵活性,Java 引入了通配符(Wildcard)机制。通配符允许我们在不指定具体类型的情况下,使用? extends T和? super T来表示泛型的类型范围,提供更大的灵活性。
一、? extends T上界通配符
? extends T 是上界通配符,表示泛型类型可以是 T 类型或 T 的子类型。通过使用 ? extends T,我们可以表示泛型参数的上界限制,使其只能是 T 或 T 的子类。下面通过示例代码进行详细讲解。
1、? extends T 的使用场景
假设我们有一个 List<? extends Number> 对象,这意味着这个列表的元素可能是 Number 类型或 Number 的子类型(如 Integer、Double)。使用 ? 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中添加Integer或Double,编译器会报错,因为我们无法确定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 的子类(例如 Integer、Long 等),但从列表中读取时只能读取到 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>中的元素类型可能是Integer或Integer的父类型(如Number、Object),因此我们只能读取Object类型的数据。- 从列表中读取元素时,编译器只能保证返回的是
Object类型,因为我们不能确定元素的具体子类类型。
3、可写入但不可读取
? super T 通配符确保我们可以向列表中添加 T 类型的数据,因为我们知道列表的元素类型是 T 或其父类,能够保证 T 的对象可以添加进去。然而,读取数据时,由于列表的元素类型不确定,编译器只会返回 Object 类型的元素。
三、? super T 和 ? extends T 的比较
1、读取与写入的差异
? extends T(上界通配符):可以从集合中读取T或其子类的数据,但不能写入数据。读取的数据类型会被推断为T或T的子类。? 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);
}
}
在这个例子中,src 是 List<? extends T>,表示我们可以从中读取 T 类型或其子类的数据。dest 是 List<? super T>,表示我们可以向其中写入 T 类型的数据。
更多推荐
所有评论(0)