类型通配符

我们知道,String类是Object类的子类,但是List不是List的子类,所以如果在方法中定义形参List,我们是不能把List赋给这个形参的。

但是如果Foo是Bar的一个子类,那么Foo[]可以直接赋给Bar[],但是G不是G的子类型。Foo[]自动向上转型为Bar[]的方式称为型变,也就是说Java的数组支持型变,但是Java集合不支持。

使用类型通配符

为了表达各种泛型List的父类,可以使用类型通配符,类型通配符是一个问号 ? ,将一个问号作为类型实参传给List集合,写作:List<?>,意思是元素类型未知的List,这个问号称为通配符,它的元素类型可以匹配任何类型。

但这种带通配符的List仅仅表示它是各种泛型List的父类,不能把元素加入其中。

设定类型通配符的上限

有一种特殊情形,程序不希望这个List<?>是任何泛型List的父类,只希望它代表某一类泛型List的父类。

例如定义三个形状类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public abstract class Shape {
public abstract void draw(Canvas c);
}
public class Circle extends Shape {
public void draw(Canvas c){
System.out.println("在画布" + c + "上画一个圆");
}
}
public class Rectangle {
System.out.println("把一个矩形画在画布" + c + "上");
}

public class Canvas {
public void drawAll(List<Shape> shapes) {
for(var s :shapes){
s.draw(this);
}
}
}

上面drawAll方法的形参是List,所以不能把List和List传入,为了表示List的父类,可以使用List<? extends Shape>,并且把Canvas改为

1
2
3
4
5
6
7
public class Canvas {
public void drawAll(List<? extends Shape> shapes) {
for(Shape s :shapes){
s.draw(this);
}
}
}

这样就可以把List对象当成List<? extends Shape>使用。Shape就称为这个通配符的上限。

此处问号代表一个未知的类型,但是这个类型一定是Shape的子类型,由于程序无法确定这个受限制的通配符的具体类型,所以不能把Shape对象或者子类的对象加入到这个泛型集合。

因为程序无法确定这个类型是什么,所以没法加入。

简而言之,这种指定通配符上限的集合,只能取元素(取出的元素总是上限的类型或者其子类),不能向集合中添加元素。

对于更广泛的泛型类,指定通配符上限就是为了支持类型型变,比如Foo是Bar的子类,这样A就相当于A<? extends Bar>的子类,可以将A直接赋值给A<? extends Bar>类型的变量,这种型变方式称为协变。

对于协变的泛型而言,他只能调用泛型类型作为返回值类型的方法(编译器会将该方法返回值当成通配符上限的类型),而不能调用泛型类型作为参数的方法,口诀是只出不进。

设定类型通配符下限

Java的通配符下限使用<? super 类型>的方式来制定,下限的作用于上限的作用刚好相反。

制定通配符的下限是为了支持类型型变,比如Foo是Bar的子类,当程序需要一个A<? super Foo>的变量时,可以把A、A赋值给它,这种型变方式称为逆变。

对于逆变的泛型集合来说,编译器只知道集合元素是下限的父类型或者该下限,但是具体哪种不知道,所以这种逆变的泛型集合只能向其中添加元素(因为实际赋值的集合元素总是逆变声明的父类,可以向上造型),但从集合取元素时,只能被当成Object类型处理。

对于逆变的泛型来说,口诀是只进不出。要出只能被当成Object类型处理,需要强制类型转换。

设定泛型形参的上限

Java泛型不仅允许在使用通配符时设定上限,而且可以在定义泛型形参的时候设定上限,用于表示传给该泛型形参的实际类型要么是该上限类型要么是该上限类型的子类。

对于更极端的情况,程序需要为泛型接口设定多个上限(至多有一个父类上限,多个接口上限),那么所有接口上限必须位于类上限的后面。

例子

1
2
3
public class Apple<T extends Number & java.io.Serializable>{
...
}

类型通配符
https://zhaoquaner.github.io/2022/05/11/Java/泛型/类型通配符/
更新于
2022年5月22日
许可协议