若是B是A的子類,而且F(B)也是F(A)的子類,那麼F即爲協變java
若是B是A的子類,而且F(B)成了F(A)的父類,那麼F即爲逆變安全
Java中的泛型有逆變和協變兩種操做,定義以下:app
<? extends A> B是A的子類,那麼List< B >是List<? extends A>的子類ide
<? supper A> B是A的子類,那麼List< B >是List<? super A>的父類ui
Java的協變逆變及其約束,都是出於對多態的應用。爲了後續說明方便,這裏先定義一系列的父子類spa
class Fruit {
public String returnMeat() {
return "generic fruit meat";
}
}
class Apple extends Fruit {
@Override
public String returnMeat() {
return "apple meat";
}
}
class GreenApple extends Apple {
@Override
public String returnMeat() {
return "green apple meat";
}
}
複製代碼
Java是支持多態的。若是一個方法的參數接收的是A類型,那麼將其子類型做爲參數,調用該方法,依然可行。 例如eatFruitMeat方法就能體現多態特性scala
@Test
public void test1() {
eatFruitMeat(new Fruit());//輸出eat generic fruit meat
eatFruitMeat(new Apple());//輸出eat apple meat
eatFruitMeat(new GreenApple());//輸出eat green apple meat
}
public void eatFruitMeat(Fruit fruit) {
System.out.println("eat "+fruit.returnMeat());
}
複製代碼
協變方法支持對傳入參數的讀操做,但不支持修改操做。以下:code
@Test
public void test1() {
List<GreenApple> greenApples = Lists.newArrayList(new GreenApple());
List<Fruit> fruits = Lists.newArrayList(new Fruit());
List<Apple> apples = Lists.newArrayList(new Apple());
eatFruitMeats(greenApples);
eatFruitMeats(fruits);//編譯錯誤1
eatFruitMeats(apples);
}
public void eatFruitMeats(List<? extends Apple> fruits) {
fruits.forEach(fruit->System.out.println("eat "+fruit.returnMeat()));
fruits.add(new Apple());//編譯錯誤2
fruits.add(new Fruit());//編譯錯誤3
fruits.add(new Object());//編譯錯誤4
}
複製代碼
逆變主要在寫的場景,即只能向逆變容器中添加,下界類型自己或其子類對象
@Test
public void test1() {
List<Fruit> fruits = Lists.newArrayList();
List<Apple> apples = Lists.newArrayList();
List<GreenApple> greenAppleLists = Lists.newArrayList();
collectFruits(fruits);
collectFruits(apples);
collectFruits(greenAppleLists);//編譯錯誤1
}
public void collectFruits(List<? super Apple> fruits) {
fruits.add(new Fruit());//編譯錯誤2
fruits.add(new Apple());
fruits.add(new GreenApple());
}
複製代碼
Java泛型支持協變和逆變,具體在使用時,會有一些約束。這些約束,須要從Java語言的特性,好比多態性,以及運行時安全性去理解。get
簡單總結協變、逆變參數的方法調用特色以下: