简单例子
//苹果Apple
public class Apple extends Fruit {
…
}
//香蕉Banana
public class Banana extends Fruit {
…
}
//动物Animal
public interface Animal {
public void eat(Fruit fruit);
public void eat(List< Fruit > fruits);
public void eat(List<? extends Fruit > fruits);
}
//猴子Monkey
public class Monkey{
public void eat(Fruit fruit){
…
}
public void eatFruits(List< Fruit > fruits){
…
}
public void eatFruitsCovariant(List<? extends Fruit > fruits){
…
}
}
关于Monkey,
Monkey monkey = new Monkey();
//1.可以正常编译
monkey.eat(new Apple ());
//2.可以正常编译
Apple apple = new Apple();
Banana banana = new Banana();
List fruits = new ArrayList<>();
fruits.add(apple);
fruits.add(banana);
monkey.eatFruits(fruits);
//3.可以正常编译
Apple apple = new Apple();
Apple apple1 = new Apple();
List< Apple > fruits = new ArrayList<>();
fruits.add(apple);
fruits.add(apple1);
monkey.eatFruitsCovariant(fruits);
//4.不可以正常编译
Apple apple = new Apple();
Apple apple1 = new Apple();
List< Apple > fruits = new ArrayList<>();
fruits.add(apple);
fruits.add(apple1);
monkey.eatFruits(fruits);
//编辑器提示类型错
前面三种情况都好理解,第四种情况为什么不行?
协变:把List< Apple >赋值给List< Fruit >即协变,具体概念可百度查询,在java中是不支持这种写法的。
、
试想一下,我有个
List< Apple > apples = new ArrayList<>();
//放两个苹果
apples.add(new Apple());
apples.add(new Apple());
//此时假如java支持
List< Fruitt > fruits = apples;
//然后,再放入一个香蕉。List变量允许放入Banana的,上面的第二种情况。
fruits.add(new Banana());
//这时候如果对原来的apples操作,取出第三个,则会导致类型转换异常.
//因为第三个是Banana而不是Apple。
Apple apple3 = apples. apples.get(2);
//所以不允许把List< Apple>赋值给List< Fruit >是有必要的。
但是这样就带来一个问题,即对于Monkey我们额外需要加入
public void eatApples(List< Apple> apples){
…
}
public void eatBananas(List< Banana> bannas){
…
}
显然这是不可接受的,因此我们需要让协变List< Fruit > fruits = apples 生效。
这里java泛型提供了一种写法,即
//这表示这个List里存放的是Fruit或其子类。
List<? extends Fruit> fruits = apples;
这让协变得以允许,但是考虑到我们前面分析的可能的类型转换异常。java里规定,协变后只可读,不可以修改。并且,由于List里存放的都是Fruit或其子类,我们并不清楚任意一个元素具体是什么类型。因此使用的时候,我们需要用Fruit变量来承接。
Fruit fruit = fruits.get(0);
//逆变,后面再写了