通配符
由于不同的泛型T
和S
之间通常没有联系,所以不允许将T
类型的对象赋值给S
类型,即使继承关系也不可以。
这时存在一个问题:如果存在一个超类作为泛型类型,想令其子类型对象都能够作为一个方法的参数,直觉上会使用超类作为参数类型,但实际上这样就不能将子类型作为泛型类型的对象传递给这个方法。有一句话很好地描述了这一限定:即使内容存在继承关系,容器之间也不存在继承关系。
// 只能向该方法传递Pair<Employee>对象
public static void printBuddies(Pair<Employee> p) {...}
// 不允许
printBuddies(new Pair<Manager>(eco, cfo));
此时就需要使用通配符<? extends Employee>
来作为方法的参数类型。此时Manager
是通配符类型的子类。注意:此时Employee
也是通配符类型的子类。
然而,使用通配符类型创建对象会导致对象中域或方法参数的泛型表示为通配符类型,由于编译器不确定其具体类型,会拒绝向其传递任何特定的类型。
package tClass;
import java.util.ArrayList;
import java.util.List;
public class WildCardTest {
public static void main(String[] args) {
List<? extends String> list = new ArrayList<String>();
list.add("hello");
}
}
使用<? extends String>
创建List对象后,用ArrayList<String>
初始化,调用add
方法编译器会报错。
Error:(9, 13) java: 对于add(java.lang.String), 找不到合适的方法
方法 java.util.Collection.add(capture#1, 共 ? extends java.lang.String)不适用
(参数不匹配; java.lang.String无法转换为capture#1, 共 ? extends java.lang.String)
方法 java.util.List.add(capture#1, 共 ? extends java.lang.String)不适用
(参数不匹配; java.lang.String无法转换为capture#1, 共 ? extends java.lang.String)
发现编译器因为不认识<? extends String>
,在捕获String
类型时,将其命名为capture#1
,所以再传递String
类型元素时,发现String
与capture#1
不匹配,编译器报错。
通配符的超类型限定
除了<? extends SuperClass>
以外,通配符类型还可以使用<? super SubClass>
。
有趣的是,<? extends SuperClass>
和<? super SubClass>
具有相反的副作用。在<? extends SuperClass>
对象中可以调用getter
方法,但不能调用setter
方法,而在<? super SubClass>
对象中相反,调用getter
方法只会返回Object
类型。
注意,super
只是限定了元素的下界,那么自然可以插入subClass
和其子类;但不能插入其超类,因为无法确定超类的具体类型。
package tClass;
import java.util.ArrayList;
import java.util.List;
public class WildCardTest {
public static void main(String[] args) {
List<? super Object> list = new ArrayList<Object>();
list.add("hello");
String str = list.get(0);
}
}
运行上述代码,add
方法可以调用,但String str = list.get(0);
编译器会报错。
Error:(10, 30) java: 不兼容的类型: java.lang.Object无法转换为java.lang.String
考虑如下方法:
public static <T extends Comparable<T>> T min(T[])
当T
类型没有扩展Comparable
接口时,程序没有很大问题。但若T
扩展了Comparable
接口或其实现了某个扩展Comparable
接口的类,由于Comparable
是泛型接口,可能导致T
实现了错误的Comparable
接口。
例如:当T
为LocalDate
时,其已经实现了ChronoLocalDate
,而该类扩展了Comparable<ChronoLocalDate>
。因此LocalDate
实现的是Comparable<ChronoLocalDate>
而非Comparable<LocalDate>
。
可以如下修改声明:
public static <T extends Comparable<? super T>> T min(T[])
此时传递T
类型的对象给compareTo
方法就能够保证安全。
无限定通配符
无限定通配符同时包含了extends
和super
的缺陷,不能调用setter
方法,getter
方法会返回Object
。
虽然这样的类型很脆弱,但当方法不需要实际类型时,就会很方便。例如:测试对象是否包含null
引用。
当然也可以将方法转换为泛型方法,但无限定通配符的版本可读性更强。
通配符捕获
当需要在方法中创建通配符类型变量时,需要设计一个泛型方法来捕获通配符,用接收通配符类型的方法调用这个泛型方法即可。当然,简单的方法直接使用泛型方法即可。
但是,使用通配符捕获存在许多限制:编译器必须能够确信通配符表达的是单个、确定的类型。例如,捕获通配符类型数组中的通配符是不合法的,因为每个通配符类型元素中的?
不是确定的。