1.通配类型的诞生
在Java泛型当中, 严格的泛型类型系统难免让人觉得有点不快。比如Pair<Apple>
并不是Pair<Fruit>
的子类, 两者并无关联。所以对于下面这样的方法是不可以传入Pair<Apple>
的。:
public static void printFruits(Pair<Fruit> f)
为了解决这个问题,Java的设计人员发明了通配类型。在通配类型中,类型参数可以代表不同的类型。例如:Pair<? extends Fruit>
可以代表任何Pair泛型,只要类型参数是Fruit的子类。那么对于上面的难题,只要将方法稍加修改:
public static void printFruits(Pair< ? extends Fruit> f)
就可以愉快地传入Pair<Apple>
啦!
2.通配类型的烦恼
当然,对于通配类型来说,也会出现各种各样的小问题。看下面代码:
Pair<Manager> managerBuddies = new Pair<>(ceo, cfo);
Pair<? extends Employee> wildcardBuddies = managerBuddies; // OK
wildcardBuddies.setFirst(lowlyEmployee); // compile-time error
这里的意思是建了一个Manage Pair,里面放了一个ceo,一个cfo,然后使用通配类型建立了一个Pair,指向一对普通Employee或者高级Manager。这里指向了刚刚创建的Manage Pair,但是之后却不能进行修改了。这是因为通配Pair的方法是这样的:
(? extends Employee) getFirst()
void setFirst(? extends Employee)
这里编译器只知道它此方法需要Employee的子类, 但是又不知道具体的类型。 由于?可能与其不符,所以编译器拒绝传递具体的类型来进行修改,偏于保守。而getFirst方法的返回值是完全可以用Employee对象及其父类对象接收的。
3.通配类型的父类bound问题
? super Manager
这个通配符被限制为所有Manager的父类型。这里与上文情况相反,可以提供参数给方法,但不能使用返回值。Pair<? super Manager>
有以下方法:
void setFirst(? super Manager)
(? super Manager) getFirst()
对于这里的setFirst方法,只能传入Manager对象或者Manager的子类。因为这里的(? super Manager)是Manager的父类,如果对其进行修改,不能传入Employee或者更高的Object,因为可能是个Manager对象。而对于getFirst方法,返回值只能拿Object对象来接收。
总结一下就是:上限的通配符–>写; 下限通配符号–>读。
4.通配类型的父类bound其他应用
public static <T extends Comparable<T>> Pair<T> minmax(T[] a) {
if(a == null || a.length == 0) return null;
T min = a[0];
T max = a[0];
for (int i = 0; i < a.length; i++) {
if (min.compareTo(a[i]) > 0) min = a[i];
if (max.compareTo(a[i]) < 0) max = a[i];
}
return new Pair<>(min, max);
}
public static void main(String[] args) {
LocalDate[] birthdays = {
LocalDate.of(1906, 12, 9),
LocalDate.of(1815, 12, 10),
LocalDate.of(1903, 12, 3),
LocalDate.of(1910, 6, 22),
};
Pair<LocalDate> mm = minmax(birthdays);
System.out.println("min = " + mm.getFirst());
System.out.println("max = " + mm.getSecond());
}
对于上面的代码,实现了找出T[]中的最大最小元素放入Pair中的操作。值得注意的是这里使用的是T extends Comparable<T>
,对于很多类如String类这里都不回会有问题,因为它实现了Comparable<String>
接口,满足T extends Comparable<T>
,但是对于有的类型如例子中的LocalDate类,继承了别的类(这里是ChronoLocalDate),而别的类实现了Comparable<ChronoLocalDate>
接口,所以这里要将T extends Comparable<T>
修改成T extends Comparable<? super T>
,即可满足条件。
5.无界通配类型
//Pair<?>有以下的方法
? getFirst()
void setFirst(?)
这里的getFirst返回值只能赋给Object对象, 而setFirst却不能被调用(setFirst(null)可以),用Object对象作为参数也不可以。而对于(raw type)Pair却可以使用任何对象作为参数来调用setFirst.
这样的类型经常在检查空指针时候使用,例如:
public static boolean hasNulls(Pair<?> p) {
return p.getFirst() == null || p.getSecond() == null;
}
//使用泛型方法代替也可以
public static <T> boolean hasNulls(Pair<T> p) {
return p.getFirst() == null || p.getSecond() == null;
}