Contravariance explained

前端 未结 5 1538
梦如初夏
梦如初夏 2020-11-27 11:57

First of, I have read many explanations on SO and blogs about covariance and contravariance and a big thanks goes out to Eric Lippert for producing such a great series on Co

5条回答
  •  忘掉有多难
    2020-11-27 12:26

    Covariance and Contravariance are not things you can observe when instancing classes. Thus it is wrong to speak about one of them when looking at a simple class instantiation, like in your example: Animal someAnimal = new Giraffe(); //covariant operation

    These terms do not classify operations. The terms Covariance, Contravariance and Invariance describe the relationship between certain aspects of classes and their subclasses.

    Covariance
    means that an aspect changes similar to the direction of inheritance.
    Contravariance
    means that an aspect changes opposite to the direction of inheritance.
    Invariance
    means that an aspect does not change from a class to its sub class(es).

    We generally regard the following aspects, when talking about Cov., Contrav. and Inv.:

    • Methods
      • Parameter types
      • Return types
      • Other signature related aspects like thrown exceptions.
    • Generics

    Let us have a look at a few examples to get a better understanding of the terms.

    class T
    class T2 extends T
     
    //Covariance: The return types of the method "method" have the same
    //direction of inheritance as the classes A and B.
    class A { T method() }
    class B extends A { T2 method() }
     
    //Contravariance: The parameter types of the method "method" have a
    //direction of inheritance opposite to the one of the classes A and B.
    class A { method(T2 t) }
    class B { method(T t) }
    In both cases, "method" gets overridden! Further, the above examples are the only legal occurrences of Cov. and Contrav. in object oriented languages.:

    • Covariance - Return types and exception throw statements
    • Contravariance - Input parameters
    • Invariance - Input and Output parameters

    Let us have a look at some counter examples to better understand the above list:

    //Covariance of return types: OK
    class Monkey { Monkey clone() }
    class Human extends Monkey { Human clone() }
     
    Monkey m = new Human();
    Monkey m2 = m.clone(); //You get a Human instance, which is ok,
                           //since a Human is-a Monkey.
     
    //Contravariance of return types: NOT OK
    class Fruit
    class Orange extends Fruit
     
    class KitchenRobot { Orange make() }
    class Mixer extends KitchenRobot { Fruit make() }
     
    KitchenRobot kr = new Mixer();
    Orange o = kr.make(); //Orange expected, but got a fruit (too general!)
     
    //Contravariance of parameter types: OK
    class Food
    class FastFood extends Food
     
    class Person { eat(FastFood food) }
    class FatPerson extends Person { eat(Food food) }
     
    Person p = new FatPerson();
    p.eat(new FastFood()); //No problem: FastFood is-a Food, which FatPerson eats.
     
    //Covariance of parameter types: NOT OK
    class Person { eat(Food food) }
    class FatPerson extends Person { eat(FastFood food) }
     
    Person p = new FatPerson();
    p.eat(new Food()); //Oops! FastFood expected, but got Food (too general).

    This topic is so sophisticated, that I could go on for a very long time. I advise you to check Cov. and Contrav. of Generics by yourself. Further, you need to know how dynamic binding works to fully understand the examples (which methods get exactly called).

    The terms arose from the Liskov substitution principle, which defines necessary criteria for modelling a data type as a sub type of another one. You might also want to investigate it.

提交回复
热议问题