java泛型通配符问题。
java中的泛型基本用法参考《java编程思想》第四版 p.353
java泛型中比较难理解的主要是类型擦除和通配符相关。
1.类型擦除
在编译期间,类型信息会被擦除,可以认为类型的检测是在编译期间进行的(见例1)
List<String> list = new ArrayList<>();
list.add("123");
list.add(new Object());//编译器在编译的时候会检测到这个类型不匹配问题
所以在生成的class文件中不包含泛型中了类型信息。
因此下面的代码会报错
class Test{
public void method1(List<String> list){
System.out.println("String list");
}
public void method1(List<Integer> list){
System.out.println("Integer list");
}
}
因为类型擦除的缘故,所以method1中的参数List<String> 和List<Integer> 会被擦除为 List 因此 method1方法签名相同,所以报错。
另外,类型擦除将擦除到他的第一个边界,可以通过下面的例子来理解
class F{
public void f(){}
}
class Test{
public static <T> void method1(T t){
t.f(); //错误,无法确定传进来的参数就是类F的实例
}
public static <T extends F> void method2(T t){
t.f();//正确,因为编译器在编译期间就检查了参数t是否为类F或者类F的子类的实例 因此可以调用f() 方法
}
}
2.类型擦除与多态
考虑下面这个例子:
public class Main1 {
public static void main(String[] args) {
F z = new Z();
z.setValue("233");
}
}
class F<T>{
private T t;
public void setValue(T t){
System.out.println("F's setValue method called");
this.t = t;
}
}
class Z extends F<String>{
@Override
public void setValue(String s) {
System.out.println("Z's setValue method called");
super.setValue(s);
}
}
从类型擦除的方面考虑 类F的setValue方法经过类型擦除后实际的签名为 public void setValue(Object obj)。
所以预测的打印结果应该是:
F's setValue method called
实际结果为
Z's setValue method called
F's setValue method called,也就是说实现了子类方法覆盖父类方法。
这其中的原因是编译器替我们生成了桥方法(bridgemethod)
可以通过反射查看类Z中有哪些方法:
Method[] methods = Z.class.getDeclaredMethods();
for( Method method : methods ){
System.out.print( method.getReturnType().getSimpleName() + " " + method.getName() + "( " );
Parameter[] parameters = method.getParameters();
for( Parameter p : parameters ){
System.out.print( p.getType().getSimpleName() + " " );
}
System.out.println( ")" );
}
打印结果:
void setValue( String )
void setValue( Object )//桥方法
其实调用的是桥方法setValue( Object )覆盖了父类的方法,然后在该方法中调用void setValue( String )。
桥方法内容:
void setValue(Object str){
this.setValue( (String)str );
}
这样就实现了覆盖。
3.通配符
3.1上界
List<? extends Animal> list;//表示此list引用可以指向泛型参数为Animal或者其子类的List实现的实例。并不是说这个list容器可以添加Animal或者Animal子类的实例。(之前一直理解错误)
在看java泛型时,最让我难以理解的就是通配符的问题,不太好理解。通过一个例子解释一下。
前提:
class Animal{
public void walk(){}
}
class Cat extends Animal{}
class Dog extends Animal{}
class Alaska extends Dog{}
public class Main2 {
public static void main(String[] args) {
List<Cat> catList = new ArrayList<Cat>();
List<Dog> dogList = new ArrayList<Dog>();
testList(catList);
testList(dogList);
}
public static void testList(List<? extends Animal> list){
list.add( new Cat() );//错误
list.add( new Dog() );//错误
list.add( new Animal() )//错误
//正确
for( Animal animal : list){
animal.walk();
}
}
}
看上面这段代码,应该能有所理解。
假设声明为List<? extends Animal> 的 list 可以添加Animal及其子类,那么调用testList方法传入catList时,可以向catList中添 加Animal及其子类, 然而catList被声明为只能存放Cat类的实例,两者矛盾,因此 声明为List<? extends Animal> 的 list 无法添加Animal及其子类(因为无法确定接收的list参数的泛型参数是什么)。 但是可以确定传递给testList方法的list中存放的对象必定为Animal或者其子类,因此迭代改list并用Animal接收。
3.2下界
List<? super Dog> list //表示此list引用可以指向泛型参数为Dog或者其父类的List实现的实例。并不是说这个list容器可以添加Dog或者Dog父类的实例。
前提:
class Animal{
public void walk(){}
}
class Cat extends Animal{}
class Dog extends Animal{}
class Alaska extends Dog{}
public class Main2 {
public static void main(String[] args) {
testList1( new ArrayList<Dog>() );
testList1( new ArrayList<Animal>());
}
public static void testList1( List<? super Dog> list ){
list.add(new Dog());
list.add( new Alaska() );
list.add( new Animal() );//错误
for( Dog dog: list ){}//错误
}
}
testList1方法可以接受泛型参数为Dog或者Dog父类的List实现的实例(List<Dog>或者List<Animal>),那么在方法testList1中往list添加Dog类或者Dog类的子类的实例是可行的,因为可以确定传递给testList1方法的list容器可以存放Dog或者Dog的父类。
但是无法确定确定具体为哪个父类。因此对list迭代并用Dog引用接收是错误的。
文章中的结论均为个人思考总结,并非权威,如有错误,遗漏,望指正。
参考资料: