经常发现有List super T>
、Set extends T>
的声明,是什么意思呢? super T>
表示包括T在内的任何T的父类, extends T>
表示包括T在内的任何T的子类,下面我们详细分析一下两种通配符具体的区别.
首先,Java的泛型在编译期会做类型擦除,这样会引起安全问题.为了安全,所有与泛型相关的异常都应该在编译期间发现,因此为了泛型的绝对安全,java在设计时做了相关的限制:
List extends T>
表示该list集合中存放的都是T的子类型(包括T自身),由于T的子类型可能有很多,但是我们存放元素时实际上只能存放其中的一种子类型(这是为了泛型安全,因为其会在编译期间生成 桥接方法(Bridge Methods),该方法中会出现强制转换,若出现多种子类型,则会强制转换失败),例子如下:
List<? extends Number> list=new ArrayList<Number>();
list.add(4.0);//编译错误
list.add(3);//编译错误
上例中添加的元素类型不止一种,这样编译器生成的桥接方法中的强制转换会失败,为了安全,Java只能将其设计成不能添加元素。
虽然List extends T>
不能添加元素,但是由于其中的元素都有一个共性–有共同的父类,因此我们在获取元素时可以将他们统一强制转换为T类型,我们称之为get原则。
对于*List super T>*其list中存放的都是T的父类型元素(包括T),我们在向其添加元素时,只能向其添加T的子类型元素(包括T类型),这样在编译期间将其强制转换为T类型时是类型安全的,因此可以添加元素,例子如下:
List<? super Number> list=new ArrayList<Number>();
list.add(2.0);
list.add(3.0);
但是,由于该集合中的元素都是T的父类型(包括T),其中的元素类型众多,在获取元素时我们无法判断是哪一种类型,故设计成不能获取元素,我们称之为put原则。
实际上,我们采用extends,super来扩展泛型的目的是为了弥补例如List
只能存放一种特定类型数据的不足.
请 记住PECS原则:生产者(Producer)使用extends,消费者(Consumer)使用super。
如 果你需要一个列表提供T类型的元素(即你想从列表中get出T类型的元素),你需要把这个列表声明成 extends T>
,比如List extends Integer>
,因此你不能往该列表中添加任何元素。
如 果需要一个列表使用T类型的元素(即你想把T类型的元素put到列表中),你需要把这个列表声明成 super T>
,比如List super Integer>
,因此你不能保证从中读取到的元素的类型。
如 果一个列表即要生产,又要消费,你不能使用泛型通配符声明列表,比如List
。
参考java.util.Collections
里的copy方法:
public static <T> void copy(List<? super T> dest, List<? extends T> src)
要从src里get所以是extends,要向dest里写入所以是super.
一个使用Consumer
的例子:
package wjw.test.java8.generic;
import java.util.function.Consumer;
public class TestGeneric {
public static void main(String[] args) {
Consumer<String> c = new Consumer<String>() { // <1>
@Override
public void accept(String s) {
System.out.println(s);
}
};
c.accept("hello lambda");
}
}
<1>: 创建了一个具体类型是String的Consumer
编译生成的字节码:
Compiled from "TestGeneric.java"
public class wjw.test.java8.generic.TestGeneric {
public wjw.test.java8.generic.TestGeneric();
Code:
0: aload_0
1: invokespecial #8 // Method java/lang/Object."":()V
4: return
public static void main(java.lang.String[]);
Code:
0: new #16 // class wjw/test/java8/generic/TestGeneric$1
3: dup
4: invokespecial #18 // Method wjw/test/java8/generic/TestGeneric$1."":()V
7: astore_1
8: aload_1
9: ldc #19 // String hello lambda
11: invokeinterface #21, 2 // InterfaceMethod java/util/function/Consumer.accept:(Ljava/lang/Object;)V
16: return
}
由于编译时进行了类型擦除,第17行的
Consumer.accept
的方法里传的参数是Object
.
生成的匿名类:
import java.util.function.*;
class LambdaOld$1 implements Consumer<String> {
LambdaOld$1() {
super();
}
@Override
public void accept(final String s) {
System.out.println(s);
}
@Override
public /* bridge */ void accept(final Object o) {
this.accept((String)o);
}
}
在桥接方法accept里,第15行会进行强制类型转换然后调用第9行的接受具体类型参数的accept方法.