探秘Java泛型通配符:? extends与? super
在Java编程的世界里,泛型无疑是一把利器,它为我们提供了类型安全的保障。而泛型通配符? extends和? super则是这把利器上的锋刃,帮助我们更灵活地处理泛型类型。今天,就让我们一起揭开它们的神秘面纱。

初识泛型通配符
泛型通配符的出现,是为了解决泛型类型在继承关系中的复杂性。简单来说,? extends T表示上界通配符,用于表示某个类T及其子类;而? super T则是下界通配符,表示某个类T及其父类。
? extends T:上界通配符
假设我们有一个水果类Fruit,以及它的两个子类Apple和Banana。如果我们需要一个方法来处理一系列水果,但又不确定具体是哪种水果,这时? extends Fruit就派上用场了。
public void processFruits(List<? extends Fruit> fruits) {
for (Fruit fruit : fruits) {
System.out.println(fruit.getName());
}
}
在这个方法中,fruits可以是一个List<Apple>、List<Banana>或者List<Fruit>。上界通配符确保了我们可以安全地读取水果的属性,但无法向列表中添加新的水果,因为编译器无法确定具体类型。
? super T:下界通配符
与上界通配符相对,下界通配符? super T用于表示某个类T及其父类。这在需要向集合中添加元素时非常有用。
public void addFruits(List<? super Apple> fruits) {
fruits.add(new Apple("Red Apple"));
fruits.add(new Apple("Green Apple"));
// fruits.add(new Banana("Yellow Banana")); // 编译错误
}
在这个例子中,fruits可以是一个List<Apple>、List<Fruit>甚至List<Object>。我们可以安全地向列表中添加Apple对象,但不能添加Banana对象,因为编译器无法保证Banana是Apple的父类。
实战应用
了解了基本概念后,让我们通过一个实际例子来看看泛型通配符的威力。
假设我们有一个水果仓库类FruitWarehouse,它包含一个存储水果的列表。我们需要实现两个方法:一个用于添加水果,另一个用于处理水果。
public class FruitWarehouse {
private List<Fruit> fruits = new ArrayList<>();
public void addFruits(List<? super Apple> newFruits) {
fruits.addAll(newFruits);
}
public void processFruits(List<? extends Fruit> fruitsToProcess) {
for (Fruit fruit : fruitsToProcess) {
System.out.println("Processing: " + fruit.getName());
}
}
}
在这个例子中,addFruits方法使用了下界通配符,确保我们可以向仓库中添加任何Apple及其父类的实例。而processFruits方法则使用了上界通配符,允许我们处理任何Fruit及其子类的实例。
总结
泛型通配符? extends和? super是Java泛型机制中的两大神器,它们在处理泛型类型的继承关系时提供了极大的灵活性。上界通配符让我们能够安全地读取数据,而下界通配符则允许我们自由地添加数据。掌握它们的使用,不仅能提升代码的健壮性,还能让我们的代码更加简洁明了。
希望通过这篇文章,你能对Java泛型通配符有一个更深入的理解,并在实际开发中灵活运用它们,让你的代码更加优雅高效。