探秘Java泛型:通配符与类型参数的魔法
在Java编程的世界里,泛型无疑是一把利剑,它赋予了我们编写更灵活、更安全的代码的能力。而在这把利剑的刃尖上,通配符和类型参数则是最为璀璨的两颗明珠。今天,就让我们一起深入探索这两者的奥秘。

泛型通配符:灵活的边界
泛型通配符主要有三种形式:?、? extends T 和 ? super T。它们各自扮演着不同的角色,为我们的代码注入了极大的灵活性。
无界通配符 ?
无界通配符 ? 代表任意类型。它常用于那些不需要具体类型信息的场景。例如:
List<?> list = new ArrayList<String>();
这里的 list 可以持有任何类型的 List,但我们无法向其中添加元素,因为我们不知道具体的类型。
上界通配符 ? extends T
上界通配符 ? extends T 表示某个类 T 或其子类的类型。它常用于读取数据的场景。例如:
List<? extends Number> list = new ArrayList<Integer>();
这里的 list 可以持有 Number 或其子类的 List,但我们仍然无法向其中添加元素,因为编译器无法确定具体是哪个子类。
下界通配符 ? super T
下界通配符 ? super T 表示某个类 T 或其父类的类型。它常用于写入数据的场景。例如:
List<? super Integer> list = new ArrayList<Number>();
这里的 list 可以持有 Integer 或其父类的 List,我们可以向其中添加 Integer 或其子类的元素。
类型参数:定制化的力量
类型参数是泛型方法的核心,它允许我们在方法中定义一个或多个类型变量,使得方法可以处理不同的类型。例如:
public <T> void printList(List<T> list) {
for (T item : list) {
System.out.println(item);
}
}
这个方法可以处理任何类型的 List,极大地提高了代码的复用性。
泛型方法的妙用
泛型方法不仅可以提高代码的复用性,还可以与通配符结合,实现更复杂的功能。例如:
public <T> void addAll(List<? super T> dest, List<? extends T> src) {
for (T item : src) {
dest.add(item);
}
}
这个方法可以将一个 List 的元素添加到另一个 List 中,即使这两个 List 的具体类型不同。
实战案例:泛型与通配符的完美结合
假设我们有一个简单的类层次结构:
class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}
我们需要一个方法来打印任意类型的动物列表:
public <T extends Animal> void printAnimals(List<T> animals) {
for (T animal : animals) {
System.out.println(animal);
}
}
同时,我们也需要一个方法来将所有动物添加到一个更大的列表中:
public void addAllAnimals(List<? super Animal> dest, List<? extends Animal> src) {
for (Animal animal : src) {
dest.add(animal);
}
}
通过这种方式,我们不仅保证了类型的安全性,还实现了代码的最大复用。
Java泛型通配符和类型参数的巧妙运用,为我们的编程之路增添了无尽的乐趣和挑战。它们不仅提升了代码的灵活性和安全性,还极大地提高了代码的复用性。希望本文能为你揭开泛型世界的神秘面纱,让你在Java编程的道路上走得更远、更稳。