Covariant and contravariant in Java

Definition 1.

1.1 Basic Concepts

Invert and covariant are used to describe the inheritance relationship after type transformation. Definition: IF A and B represent type, F (⋅) represents type transformation, and ≤ represents inheritance (for example, A≤B indicates that A is A subclass derived from B)

  • F (⋅) is contravariant, and f(B) ≤ f(A) is valid when A ≤ B;
  • F (⋅) is covariant, and f(A) ≤ f(B) is valid when A ≤ B;
  • F (⋅) is invariant. When A ≤ B, neither of the above two formulas holds, i.e., f(A) and F (B) have no inheritance relationship with each other.

1.2 Li substitution principle

Richter’s substitution principle can be described as follows: “Derived (subclass) objects may replace their base (superclass) objects in a program.”

1.3 interpretation

In general terms, covariant and contravariant describe the changes in inheritance before and after type conversion. The type conversion here is most common in Java with generic classes. The most obvious use of inheritance is in substitution. When A class A can replace another class B in A program, then A is A subclass of B.

2. Java arrays

2.1 Array covariant

Java arrays are covariant. That is, if there is type A ≤ B, then there is also type A[] ≤ B[] between array type A[] and array type B[].

The following is an example:

public class Tmp {
    static class Fruit {
        public void name(a) {
            System.out.println("fruit"); }}static class Apple extends Fruit {
        public void name(a) {
            System.out.println("apple"); }}static class GreenApple extends Apple {
        public void name(a) {
            System.out.println("green apple"); }}@Test
    public void test(a) {
        Fruit[] fruits = new Apple[10]; / / 1.
        fruits[0] = new Apple();
        fruits[1] = new Apple();
        fruits[2] = new Fruit(); / / (2) throw Java. Lang. ArrayStoreException}}Copy the code

In this example, Apple[] is a subclass of Fruit[], so it is valid. However, new Apple[10] can only put apple-class elements, so the execution of ② will be abnormal, but it will compile normally.

2.2 Causes of array covariant

If the array is not covariant, Apple[] cannot be passed to an input of Fruit[], and the array will lose its polymorphic flexibility. But polymorphism presents a new problem. The reference type (Fruit[]) is not the same as the actual type (Apple[]), causing Apple[] to be placed in the Fruit element. If the Apple[] Array accepts a Fruit object regardless, it may result in a non-Apple Fruit object being read from the Apple[] Array, which is not an Array definition. Fortunately, arrays do type checking when they accept objects. So that’s not going to happen.

To sum up, arrays are designed to be covariant because of the flexibility they provide and because they are still safe for type checking.

3. Java generics

3.1 Generic types do not change

In general, if class A ≤ B, there is no clear inheritance between the generic T and the generic T.

In a word, generics are normally immutable.

If generics are always the same, and there is no inheritance between all generic classes, there will be no polymorphism in generic classes at all, and the meaning of generics will be greatly diminished. So Java provides generic constraints for contravariant and covariant implementations.

3.2 Generic contravariant

Generic inversion, i.e., if class A ≤ B, then T ≤ T. Java generics use the super keyword to implement inversion.

public class Tmp {
    static class Fruit {
        public void name(a) {
            System.out.println("fruit"); }}static class Apple extends Fruit {
        public void name(a) {
            System.out.println("apple"); }}static class GreenApple extends Apple {
        public void name(a) {
            System.out.println("green apple"); }}@Test
    public void test1(a) {
        List<Fruit> fruits = Arrays.asList(new Fruit(), new Fruit(), new Apple());
        eat(fruits); //

        List<GreenApple> greenApples = Arrays.asList(new GreenApple());
        eat(greenApples); // error
    }

    private void eat(List<? super Apple> list) {
        System.out.println("eat"); }}Copy the code

List

= List
subclass, so you can use the super keyword to achieve inverse.

3.3 Generic covariant

Generic covariant, i.e. if class A ≤ B, then T ≤ T. Java generics implement covariation using the extends keyword.

public class Tmp {
    static class Fruit {
        public void name(a) {
            System.out.println("fruit"); }}static class Apple extends Fruit {
        public void name(a) {
            System.out.println("apple"); }}static class GreenApple extends Apple {
        public void name(a) {
            System.out.println("green apple"); }}@Test
    public void test1(a) {
        List<Fruit> fruits = Arrays.asList(new Fruit(), new Fruit(), new Apple());
        eat(fruits); //

        List<GreenApple> greenApples = Arrays.asList(new GreenApple());
        eat(greenApples); // Execute normally
    }

    private void eat(List<? extends Apple> list) {
        System.out.println("eat"); }}Copy the code

List

is a List
, so you can implement covariation using the extends keyword.

4. To summarize

This paper focuses on covariant and contravariant, and enumerates examples of array and generics to illustrate the concept of covariant contravariant. Genericizing a class or array raises the question of how to determine the inheritance relationship between the transformed class (generic class or array) and the original class. The concept of covariant and contravariant is put forward to determine the inheritance relationship of transformed classes. Many languages have this in mind when implementing generics, and as far as I know, languages like C# and kotlin take a similar approach to Java.

The level is limited, inevitably there are mistakes and omissions. If you have any questions please contact me ([email protected]), we can discuss together.