一、概要
java范围有三种形式:
- <? extends T>: 上界通配符, ?表示继承自T的类(沿着类图,上边界是T)。频繁往外读取内容,适合采用上界通配符。
- <? super T>:下界通配符,?表示T及其父类(沿着类图,下边界是T)。频繁插入内容,适合采用下界通配符。
- <?>:某个类型。单纯表示引用某一类型,不进行插入或读取
- 如果频繁读取或插入,尽量避免使用通配符,以免数据丢失。
二、构建类树
为了清晰表明概要中的意识,先构建类树,用于概念细化、代码解读。此处解读参考Think In Java书的案例。
class Fruit {
public void getClassName() {System.out.println(this.getClass().getSimpleName());}
}
class Apple extends Fruit {}
class Jonathan extends Apple {}
class Maigold extends Apple {}
class Orange extends Fruit { }
class Mandarin extends Orange {}
class Tangerine extends Orange {}
以上即为本次案例所使用的类图及继承关系
如上图,
- <? extends Apple>为上界通配符,即上界是Apple,?表示继承自Apple的类,作用范围是红色三角内部的类
- <? super Apple>为下界通配符,即下界是Apple,?表示Apple及其父类,作用范围是蓝色三角内部的类。
三、代码解读
3.1 泛型容器无法进行向上转型
import java.util.ArrayList;
import java.util.List;
public class NonCovariantGenerics {
// 编译错误,无法进行向上转型
List<Fruit> flist = new ArrayList<Apple>();
}
上述代码会引发编译错误,无法将List<Apple>向上转型为List<Fruit>,即无法将“涉及Apple的范型赋给涉及Fruit的范型”。当然这违背生活常识,Apple竟然无法放到Fruit里。但从代码角度思考,Apple的List不是Fruit的List,因为List<Apple>将持有Apple及其子类,而List<Fruit>能够持有Fruit子类,范围远大于List<Apple>。
如果想在泛型容器间建立转型关系,使代码描述更加人性化,即面向对象,则需要采取通配符。
3.2 上界通配符 <? extends T>
import java.util.ArrayList;
import java.util.List;
public class GenericsAndCovariance {
public static void main(String[] args) {
// 上界通配符进行容器的向上转型
List<? extends Fruit> flist = new ArrayList<Apple>();
// 以下均会引起编译错误
// flist.add(new Apple());
// flist.add(new Fruit());
// flist.add(new Object());
flist.add(null); // Legal but uninteresting
// We know that it returns at least Fruit:
Fruit f = flist.get(0);
f.getClassName(); //List里只有空指针,无法调用该函数
}
}
通过上述代码例子得知,List<? extends Fruit>并不是说该List可以持有任何类型的Fruit,通配符引用的是明确的类型。因此,该处可以这样解译“List<? extends Fruit> 持有某个具体类型,该类型具体是什么我不清楚,但我可以具定它是Fruit的子类型”。
可见,因为无法确定List<? extends Fruit>具体持有什么类型,故向内部填加对象比较危险,故调用add(Object object)会引发失入。但是可以使用get(int index)方法。
可见,上界通配符主要支持读取操作,应尽量避免写入操作,以免引发异常。
3.3 绕过编译器
import java.util.*;
public class CompilerIntelligence {
public static void main(String[] args) {
List<? extends Fruit> flist =
Arrays.asList(new Apple(), new Fruit(), new Orange(), new Jonathan());
Apple apple = (Apple)flist.get(0); // 利用get获取类
apple.getClassName();
for (int i = 1; i < flist.size(); i++) {
Fruit temp = flist.get(i); //利用动态编译,指向不同的类
temp.getClassName();
}
flist.contains(new Apple()); // 调用boolean contains(Object o);
flist.indexOf(new Apple()); // 调用int indexOf(Object o);
//flist.add(new Apple()); //调用boolean add(E e);失败
}
}
/**
* OUTPUT
* Class name is : Apple
* Class name is : Fruit
* Class name is : Orange
* Class name is : Jonathan
*/
上述代码可知:
- 借助Arrays.asList将Fruit及其子类,以List的形式存储到List<? extends Fruit>,变相实现了add,只是有些机械化。
- 利用强制转型,获取对应的Apple类型
- 利用动态编译,动态引用Orange, Jonathan类型
- contains 和indexOf方法的参数是Object,不涉及通配符,所以可以成功执行
- add的方法参数是泛型,在本案例中即是<? extends Fruit>,是通配符,无法明确具体类型,所以编译失败
3.4 下界通配符<? super T>
下界通配符,直白些,可以理解为支持向下转型,其作用域可以参见本篇第二章图的蓝色区域
import java.util.*;
public class SuperTypeWildcards {
public static void main(String[] args ) {
//支持向下转型,不支持向上转型
List<? super Apple> appleList = new ArrayList<Fruit>();
//List<? super Apple> appleListTwo = new ArrayList<Jonathan>();转型失败
//appleList = new ArrayList<Jonathan>();转型失败,无法进行
appleList.add(new Apple());
//appleList.add(new Fruit());编译失败,List<Fruit>可以向下转型为List<? super Apple>,
// 但是转型后,就只能放Apple相关类了
appleList.add(new Jonathan());
appleList.add(new Maigold());
//appleList.add(new Orange());编译失败,Orange不是Apple
for (int i = 0; i < appleList.size(); i++) {
Apple temp = (Apple) appleList.get(i);
temp.getClassName();
}
}
}
/**OUTPUT
* Class name is : Apple
* Class name is : Jonathan
* Class name is : Maigold
*/
上述代码得知
- <? super T>支持向下转型,不支持向上转型,作用范围参见本篇图2。我把这理解为下界通配符的通俗含义
- <? super T>支持插入数据类,插入的数据必须是Apple及其子类
- <? super T>通过强制类型转换,也可以取数据,可能会存在数据丢失情况
3.5 无界通配符 <?>
用一句歌词来表达无界通配符,“我不知道你是谁,但我知道你为了谁”。无界通配符作用等价于使用原生类型,其表明使用泛型进行类型引用,在不清楚或者无需清楚具体类型的前提下。