java泛型详解
1. extends与super
extends关键字限制了泛型类可以使用的泛型参数类型的上限(Upper Bound)
super关键字限制了泛型类可以使用的泛型参数类型的下限(Lower Bound)
先定义几个简单的Class
class Food { } class Fruit extends Food { } class Meat extends Food { } class Apple extends Fruit { } class Orange extends Fruit { } class Pork extends Meat { } class Beef extends Meat { }
extends解析
直接赋值的限制
以泛型类List为例,如果我们声明下面这样的一个List
List<? extends Fruit> fruitList;
那么后续给这个fruitList赋值时,这个值允许使用的泛型参数的上限就是Fruit,也就是Fruit或者Fruit的子类。所以下面的代码是可以编译通过的
fruitList = new ArrayList<Apple>(); fruitList = new ArrayList<Orange>();
下面的代码则会引发编译失败
fruitList = new ArrayList<Food>();//Food不是Fruit的子类,编译失败
泛型方法参数的限制
在上面的例子中,fruitList的含有泛型参数的方法,例如add,是受到极大的限制的:
fruitList.add(new Object());//编译错误 fruitList.add(new Food());//编译错误 fruitList.add(new Fruit());//编译错误 fruitList.add(new Apple());//编译错误 fruitList.add(null);//编译通过
可以发现,除了不带有Class类型信息的null,任何Class(甚至包括Object)的实例,都不能作为add方法的泛型参数,这是为什么呢?
因为extends关键字只是限制了泛型类可以使用的泛型参数的上限,编译器并不知道fruitList实际指向的实例使用的泛型参数具体是什么,可能是Fruit,也可能是Apple或者Orange。
这句话不太好理解,但是如果把fruitList直接替换成已经正确设置了泛型参数的泛型类的实例,然后调用泛型方法,可能会比较好懂。
例如在泛型参数为Orange时,上面的代码可以转换为下面的形式:
new ArrayList<Orange>().add(new Object());//编译错误 new ArrayList<Orange>().add(new Food());//编译错误 new ArrayList<Orange>().add(new Fruit());//编译错误 new ArrayList<Orange>().add(new Apple());//编译错误 new ArrayList<Orange>().add(null);//编译通过
也就是说,编译器采用了非常保守的策略来确保类型安全(只允许发生隐式的从子类向父类的类型转换)。
泛型方法返回值的限制
在上面的例子中,由于可以确定fruitList对应的实例使用的泛型参数必然是Fruit或者Fruit的子类,所以如果有方法的返回值是泛型参数,那么可以确定这个返回值必然也是Fruit或者Fruit的子类了
Fruit fruit = fruitList.get(0);
上面这行代码是IDEA的自动补全返回值类型的效果。
super解析
直接赋值的限制
以泛型类List为例,如果我们声明下面这样的一个List
List<? super Fruit> fruitList;
那么后续给这个fruitList赋值时,这个值允许使用的泛型参数的下限就是Fruit,也就是Fruit或者Fruit的父类。所以下面的代码是可以编译通过的
fruitList = new ArrayList<Fruit>();//编译通过 fruitList = new ArrayList<Food>();//编译通过
而下面的代码则会引发编译失败
fruitList = new ArrayList<Apple>();//编译错误
泛型方法参数的限制
在上面的例子中,fruitList的含有泛型参数的方法,例如add,也受到了一定的限制:
fruitList.add(new Object());//编译错误 fruitList.add(new Food());//编译错误 fruitList.add(new Fruit());//编译通过 fruitList.add(new Apple());//编译通过 fruitList.add(null);//编译通过
可以发现,只允许使用Fruit或者Fruit的子类的实例作为参数,这又是为啥呢?
因为super关键字是限制了泛型类可以使用的泛型参数的下限,泛型参数无论是什么,都必然是Fruit或者Fruit的父类,这必然也是Fruit的子类的父类了。
所以只要给泛型方法传入Fruit或者Fruit子类的实例作为参数,最多只会引发隐式类型转换,不会导致异常。
以泛型参数为Food为例,把上面的代码转换一下:
new ArrayList<Food>().add(new Object());//编译错误 new ArrayList<Food>().add(new Food());//编译通过,但是如果换另外一种父类就出错了 new ArrayList<Food>().add(new Fruit());//编译通过 new ArrayList<Food>().add(new Apple());//编译通过 new ArrayList<Food>().add(null);//编译通过
这也是编译器为了确保类型安全作出的努力
泛型方法返回值的限制
在上面的例子中,由于无法确定fruitList对应的实例使用的泛型参数到底是什么玩意(可能是Fruit,也可能是Food,甚至可能是Object),所以只能用万能的Object作为返回值类型了:
Object object = fruitList.get(0);
上面这行代码是IDEA的自动补全返回值类型的效果。
PECS法则
Producer->extends Consumer->super
如果你希望某个泛型类只能生产(方法有泛型返回值)受限定的元素(Producer),那么就该用extends关键字来做限定
如果你希望某个泛型类只能消费(方法有泛型参数)受限定的元素(Consumer),那么就该用super关键字来做限定
小结
extends和super的限定初看非常难懂,但是如果把泛型类的泛型参数试着实例化并且展开一下,就好理解多了。
这些奇怪的限制,主要是为了在任何情况下都能保证类型安全(只发生隐式的子类向父类的转换)