在Java泛型中常用的通配符有两种
-
<? extends T> 设置参数化类型的上界,表示参数化类型只能是T、T的子类或实现类。
-
<? super T> 设置参数化类型的下界,表示参数化类型只能是T或T的父类。
泛型的通配符常使用在容器中,下面来辨析以上两种通配符在容器中使用时的差异。
#一 <? extends T>
List<? extends Number> foo3 = new ArrayList<Number>();
List<? extends Number> foo3 = new ArrayList<Integer>();
List<? extends Number> foo3 = new ArrayList<Double>();
##1.从List中读取
- 可以从foo3中读取出Number类型,因为右边的泛型参数都是Number的子类。
- 无法从foo3中读取出Integer类型,因为foo3可能是对ArrayList<Double>的引用。
- 无法从foo3中读取出Double类型,因为foo3可能是对ArrayList<Integer>的引用。
##2.写入List
- 无法写入Number类型,因为foo3可能是List<Double>或List<Integer>。
- 无法写入Integer类型,因为foo3可能是List<Double>或List<Number>。
- 无法写入Double类型,因为foo3可能是List<Integer>或List<Number>。
实际上你无法像向foo3中写入任何类型,因为无法确定foo3的实际类型。你能确保的是,从foo3中读取出的对象一定是Number或Number的子类。
#二 <? super T>
List<? super Integer> foo3 = new ArrayList<Integer>();
List<? super Integer> foo3 = new ArrayList<Number>();
List<? super Integer> foo3 = new ArrayList<Object>();
##1.从List中读取
- 无法读取Integer类型,因为foo3可能是List<Number>或List<Object>。
- 无法读取Number类型,因为foo3可能是List<Object>。
- 只能读取Object类型,因为foo3中的内容是Object或者是Object的子类。
##2.写入List
- 可以写入Integer与Integer的子类,因为它们都是Number,Object的子类。
- 无法写入Double类,因为foo3可能是List<Integer>。
- 无法写入Number类,因为foo3可能是List<Integer>。
- 无法写入Object类,因为foo3可能是List<Integer>。
在使用extends与super时,可参照PECS("Producer Extends, Consumer Super")规则:
- "Producer Extends" 如果容器的作用是用来生产某个对象(从容器中取出某个类型的对象),那么就应该用 <? extends T>。
- "Consumer Super" 如果容器的作用是用来消费某个对象(向容器中放置某个类型的对象),那么就应该使用<? super T>。
- 如果需要同时取出与存放某个类型的对象,那么就应该明确指明对象,例如List<Integer>