Java中的逆變與協變

什麼是逆變與協變

協變(Covariance)

若是B是A的子類,而且F(B)也是F(A)的子類,那麼F即爲協變java

逆變(Contravariance)

若是B是A的子類,而且F(B)成了F(A)的父類,那麼F即爲逆變安全

Java中的逆變與協變

Java中的泛型有逆變和協變兩種操做,定義以下:app

協變

<? extends A> B是A的子類,那麼List< B >是List<? extends A>的子類ide

逆變

<? supper A> B是A的子類,那麼List< B >是List<? super A>的父類ui

Java中逆變與協變的約束

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
    }
複製代碼
  • 編譯錯誤1: eatFruitMeats方法接受的List<? extends Apple>的子類,顯然List< Fruit >不是其子類
  • 編譯錯誤2,3,4: eatFruitMeats方法在被調用前,並不知道最終調用方,傳遞進來的具體是哪個子類?有多是List< Apple >,也有多是List< GreenApple >,因此貿然向其中添加任何對象,都是可能出錯,好比你不能把一個Apple對象放進List< GreenApple >。爲了防止這些可能的錯誤,編譯器提早進行了約束限制。

逆變約束

逆變主要在寫的場景,即只能向逆變容器中添加,下界類型自己或其子類對象

@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());
}
複製代碼
  • 編譯錯誤1: 因爲是逆變,因此List< GreenApple > 是List<? super Apple>的父類。因此不能將greenAppleLists 做爲參數調用collectFruits方法,由於不知足Java方法參數的多態性要求,即只能傳本類或子類的要求
  • 編譯錯誤2: 若是調用方傳遞的是List< Apple >,那往其中添加父類Fruit對象,在運行時確定會報錯,爲了不這種狀況,編譯器提早報錯。

總結

Java泛型支持協變和逆變,具體在使用時,會有一些約束。這些約束,須要從Java語言的特性,好比多態性,以及運行時安全性去理解。get

簡單總結協變、逆變參數的方法調用特色以下:

協變參數

  • 只接受本身的子類。協變的父子關係,同類本來的父子關係一致。如GreenApple是Apple的子類,List< GreenApple >是List<? extends Apple>的子類
  • 對寫有約束,只能用於讀

逆變參數

  • 只接受本身的子類。逆變的父子關係,同類本來父子關係相反。如GreenApple是Apple的子類,List< GreenApple >是List<? super Apple>的父類
  • 只能寫入下界的子類,本例中,只能向List中寫入Apple及Apple的子類

參考連接

medium.com/@sinisalouc…

相關文章
相關標籤/搜索