泛型(二)——通配符和上下界限制
泛型的上下界限制一直不明白什么意思,跑了一遍代码终于明白了。
在讨论之前有以下几个类:
//Food.java
public class Food {
public String toString(){
return this.getClass().getName();
}
}
//Fruit.java
public class Fruit extends Food {}
//Apple.java
public class Apple extends Fruit {}
1. 协变
java中数组是协变的,以下语句可以成立:
Fruit[] fruits = new Apple[]{new Apple()};
而泛型不是协变的,我们有这样一个泛型类:
public class AGenericClass <T> {
private T t;
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
}
以下的语句也是不成立的:
AGenericClass<Fruit> aGenericClass = new AGenericClass<Apple>(); //Error
也就是说AGenericClass<Apple>
和AGenericClass
是不一样的
为了让容器类满足父类的容器能够容纳子类的需求,就有了边界和通配符的使用。
2. 通配符和边界
泛型本质上就是一个类型参数,也就是把类型作为参数进行使用。在定义形参的时候只能使用extends
来限定边界,意味着只接受某类的子类作为类型参数。例如:
class BGenericClass<Apple extends Fruit> {
private Apple apple;
public Apple getApple() {
return apple;
}
public void setApple(Apple apple) {
this.apple = apple;
}
}
这里的Apple是定义的形参,并不是指真正的类Apple
。这里定义的形参把类型参数约束为Fruit
的子类。形参不能使用通配符?
,通配符只有作为实参输入类型参数的时候才能使用。
实参可以使用通配符?
,意味着任何类,也可以配合extends
和super
来定义上下边界。例如:
AGenericClass<?> aGenericClass = new AGenericClass<Fruit>();
此时的声明的aGenericClass
具有类型参数?
,这是一个通配符,不是任何一个确定的类,这个对象的setT(T t)
方法要求输入一个?
类型的对象,这个?
类型不能确定是任何类的基类,因此无法自动向上转型。同时?
类型不具有实际的类定义,所以其他类也不能够向下转型成?
,因此以类型参数T
作为方法参数的set(T t)
方法无法使用:
aGenericClass.setT(new Fruit()); //Error
而同理这个对象的getT()
方法返回了一个T类型的对象,在编译阶段没有这个对象的类型信息,只能向上转型成为Object
对象:
Object fruit = aGenericClass.getT();
在配合使用extends
和super
的时候也有类似的情况,分别讨论extends
和super
的使用
2.1. extends
extends
使得声明的泛型对象可以接受类型参数为子类的泛型对象,如下:
AGenericClass<? extends Fruit> aGenericClass = new AGenericClass<Apple>();
和?
类似,这样是有代价的:
带有<? extends Fruit>泛型参数的对象无法使用带有类型参数作为方法参数的方法
这句话有点绕,其实意思就是:1. 假如你用了<? extends Fruit>
,2. 那么类中定义的方法的参数列表中带有T
的你就不能用。即以下的语句是不成立的:
aGenericClass.setT(new Apple()); //Error
aGenericClass.setT(new Fruit()); //Error
也就是说,方法接受的参数是Fruit
的子类,编译器不能自动帮你向下转型
带有<? extends Fruit>泛型参数的对象可以使用以类型参数作为返回值的方法
意思就是,假如一个方法返回T
,那么这个方法可用。即:
Fruit fruit = aGenericClass.getT(); //ok
getT()方法返回了一个Fruit
类型的子类,这个类一定是Fruit
或者它的子类,因此可以自动地向上转型成为Fruit
2.2. super
super
使得声明的泛型对象可以接受类型参数为父类的泛型对象,如下:
AGenericClass<? super Fruit> aGenericClass = new AGenericClass<Food>();
带有<? super Fruit>泛型参数的对象不可以使用以类型参数作为返回值的方法
即:
Fruit fruit = aGenericClass.getT(); //Error
getT()方法返回了一个Fruit
类型的父类,这个父类的具体类型不确定,总之不一定是Fruit
,但是它可以向上转型成为Object,或者显式向上转型成为Fruit
Object object = aGenericClass.getT(); //ok
Fruit fruit2 = (Fruit) aGenericClass.getT(); //ok
带有<? super Fruit>泛型参数的对象可以使用带有类型参数作为方法参数的方法
即:
aGenericClass.setT(new Apple());
aGenericClass.setT(new Fruit());
setT(T t)方法要求输入一个是Fruit
类型或者它的父类,因此可以保证Fruit可以向上转型成为这个对象的类型。