Super通配符
约 1660 字大约 6 分钟
2025-07-17
6.1 super 通配符的使用
前面我们已经了解到,泛型类型之间不存在继承关系,例如 Pair<Integer>
不是 Pair<Number>
的子类。
现在考虑一个 set
方法,其作用是设置 Pair
对象的 first
和 last
属性:
void set(Pair<Integer> p, Integer first, Integer last) {
p.setFirst(first);
p.setLast(last);
}
上述方法只能接受 Pair<Integer>
类型的参数。如果我们想让该方法接受 Pair<Number>
、Pair<Object>
等类型,因为 Number
和 Object
是 Integer
的父类,并且 setFirst(Number)
和 setFirst(Object)
实际上可以接受 Integer
类型,这时就需要使用 super
通配符。
使用 super
通配符改写后的方法如下:
void set(Pair<? super Integer> p, Integer first, Integer last) {
p.setFirst(first);
p.setLast(last);
}
Pair<? super Integer>
表示该方法接受所有泛型类型为 Integer
或 Integer
父类的 Pair
类型。例如,Pair<Integer>
、Pair<Number>
和 Pair<Object>
都可以作为参数传递给这个 set
方法。
以下代码展示了 super
通配符的用法:
public class Main {
public static void main(String[] args) {
Pair<Number> p1 = new Pair<>(12.3, 4.56);
Pair<Integer> p2 = new Pair<>(123, 456);
setSame(p1, 100);
setSame(p2, 200);
System.out.println(p1.getFirst() + ", " + p1.getLast()); // 100, 100
System.out.println(p2.getFirst() + ", " + p2.getLast()); //200, 200
}
static void setSame(Pair<? super Integer> p, Integer n) {
p.setFirst(n);
p.setLast(n);
}
}
class Pair<T> {
private T first;
private T last;
public Pair(T first, T last) {
this.first = first;
this.last = last;
}
public T getFirst() {
return first;
}
public T getLast() {
return last;
}
public void setFirst(T first) {
this.first = first;
}
public void setLast(T last) {
this.last = last;
}
}
在这个例子中,setSame
方法接受 Pair<? super Integer>
类型的参数,这意味着可以传入 Pair<Integer>
、Pair<Number>
或 Pair<Object>
类型的实例。
6.2 方法签名与返回值
对于 Pair<? super Integer>
的 setFirst()
方法,其方法签名实际上是:
void setFirst(? super Integer);
因此,可以安全地传入 Integer
类型。
而对于 Pair<? super Integer>
的 getFirst()
方法,其方法签名实际上是:
? super Integer getFirst();
? super Integer
表示 Integer
类型或 Integer
的任何父类。这意味着我们无法使用 Integer
类型来接收 getFirst()
的返回值,下面的语句将无法通过编译:
Integer x = p.getFirst(); // 编译错误
因为如果传入的实际类型是 Pair<Number>
,编译器无法将 Number
类型转型为 Integer
。
唯一可以接收 getFirst()
方法返回值的是 Object
类型:
Object obj = p.getFirst();
因此,使用 <? super Integer>
通配符表示:
- 允许调用
set(? super Integer)
方法传入Integer
的引用。 - 不允许调用
get()
方法获得Integer
的引用。
唯一例外是可以获取 Object
的引用:Object o = p.getFirst()
。
换句话说,使用 <? super Integer>
通配符作为方法参数,表示方法内部代码对于参数只能写,不能读。
6.3 Extends 和 Super 通配符的对比
作为方法参数,<? extends T>
类型和 <? super T>
类型的区别在于:
类型参数 | 合法 | 非法 | 例外情况 |
---|---|---|---|
<? extends T> | 调用读方法 T get() 获取 T 的引用 | 调用写方法 set(T) 传入 T 的引用 | 允许传入 null |
<? super T> | 调用写方法 set(T) 传入 T 的引用 | 调用读方法 T get() 获取 T 的引用 | 允许获取 Object 对象 |
一个是允许读不允许写,另一个是允许写不允许读。
6.4 Collections.copy() 方法示例
Java 标准库的 Collections
类定义的 copy()
方法很好地展示了 extends
和 super
的意图:
public class Collections {
// 把src的每个元素复制到dest中:
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
for (int i=0; i<src.size(); i++) {
T t = src.get(i);
dest.add(t);
}
}
}
它的作用是把一个 List
的每个元素依次添加到另一个 List
中。它的第一个参数是 List<? super T>
,表示目标 List
,第二个参数 List<? extends T>
,表示要复制的 List
。
在这个 copy()
方法中:
- 对于类型
<? extends T>
的变量src
,可以安全地获取类型T
的引用。 - 对于类型
<? super T>
的变量dest
,可以安全地传入T
的引用。
copy()
方法内部不会读取 dest
,因为不能调用 dest.get()
来获取 T
的引用。copy()
方法内部也不会修改 src
,因为不能调用 src.add(T)
。这是由编译器检查来实现的。如果在方法代码中意外修改了 src
,或者意外读取了 dest
,就会导致一个编译错误。
这个 copy()
方法的另一个好处是可以安全地把一个 List<Integer>
添加到 List<Number>
,但是无法反过来添加:
// copy List<Integer> to List<Number> ok:
List<Number> numList = ...;
List<Integer> intList = ...;
Collections.copy(numList, intList);
// ERROR: cannot copy List<Number> to List<Integer>:
Collections.copy(intList, numList);
而这些都是通过 super
和 extends
通配符,并由编译器强制检查来实现的。
6.5 PECS 原则
何时使用 extends
,何时使用 super
? 为了便于记忆,可以使用 PECS 原则:Producer Extends Consumer Super。
- 生产者 (Producer): 提供数据的一方。
- 消费者 (Consumer): 接收数据的一方。
即:如果需要返回 T
,它是生产者(Producer),要使用 extends
通配符;如果需要写入 T
,它是消费者(Consumer),要使用 super
通配符。
还是以 Collections
的 copy()
方法为例:
public class Collections {
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
for (int i=0; i<src.size(); i++) {
T t = src.get(i); // src是producer
dest.add(t); // dest是consumer
}
}
}
需要返回 T
的 src
是生产者,因此声明为 List<? extends T>
,需要写入 T
的 dest
是消费者,因此声明为 List<? super T>
。
6.6 无限定通配符
Java 的泛型还允许使用无限定通配符(Unbounded Wildcard Type),即只定义一个 ?
:
void sample(Pair<?> p) {
}
因为 <?>
通配符既没有 extends
,也没有 super
,因此:
- 不允许调用
set(T)
方法并传入引用(null
除外)。 - 不允许调用
T get()
方法并获取T
引用(只能获取Object
引用)。
换句话说,既不能读,也不能写,那只能做一些 null
判断:
static boolean isNull(Pair<?> p) {
return p.getFirst() == null || p.getLast() == null;
}
虽然 <?>
允许传入任何类型的 Pair
作为参数,提供了一定的灵活性,但它牺牲了类型安全和可操作性。大多数情况下,可以引入泛型参数 <T>
消除 <?>
通配符,从而提供更强的能力和灵活性:
static <T> boolean isNull(Pair<T> p) {
return p.getFirst() == null || p.getLast() == null;
}
<?>
通配符有一个独特的特点,就是:Pair<?>
是所有 Pair<T>
的超类:
public class Main {
public static void main(String[] args) {
Pair<Integer> p = new Pair<>(123, 456);
Pair<?> p2 = p; // 安全地向上转型
System.out.println(p2.getFirst() + ", " + p2.getLast());
}
}
class Pair<T> {
private T first;
private T last;
public Pair(T first, T last) {
this.first = first;
this.last = last;
}
public T getFirst() {
return first;
}
public T getLast() {
return last;
}
public void setFirst(T first) {
this.first = first;
}
public void setLast(T last) {
this.last = last;
}
}
上述代码是可以正常编译运行的,因为 Pair<Integer>
是 Pair<?>
的子类,可以安全地向上转型。