Find a paragraph of covariant, contravariant more formal definition:

12. Invert and covariant are used to describe the inheritance relationship after A type conversion. If A or B represent A type, f() represents A conversion. When A ≦ B, f(A) ≦ f(B) is covariant. When A ≦ B, f(B) ≦ f(A) is contravariant. If neither of the above relations is true, i.e., (A) and f(B) do not inherit from each other, it is said to be invariant.

Covariant and contravariant support can be achieved in Java through the following generic wildcards:

  • ? extendsMake generics support covariation. The modified generic collection can only be read and cannot be modified. The modification here only means adding elements to the generic collection, ifremove(int index)As well asclearOf course you can.
  • ? superMake generics contravariant. The modified generic collection can only be modified and cannot be read. By unread, I mean it cannot be read as a generic typeObjectRead out again strong turn of course is also ok.

Let’s take an animal and look at the code.

Void eat() {system.out.println (" I am + myName() + "+ myFavoriteFood()); } abstract String myName(); abstract String myFavoriteFood(); } class extends Animal {@override String myName() {return "Fish "; } @override String myFavoriteFood() {return "favoritefood "; }} class Cat extends Animal {@override String myName() {return "Cat "; } @override String myFavoriteFood() {return "fish "; } } public static void extendsFun() { List<Fish> fishList = new ArrayList<>(); fishList.add(new Fish()); List<Cat> catList = new ArrayList<>(); catList.add(new Cat()); List<? extends Animal> animals1 = fishList; List<? extends Animal> animals2 = catList; animals2.add(new Fish()); Animal animal1 = animals1.get(0); Animal animal2 = animals2.get(0); animal1.eat(); animal2.eat(); } // Output result: I am a fish, I like to eat shrimp. I am a cat, I like to eat dried fishCopy the code

Covariant is like having collections that each extends Animal of a particular kind, but don’t tell you which one is a fish or which one is a cat. So while you can read an animal from any set, no problem, you can’t store a fish in the fish set, because you don’t know which set contains fish and which set contains cats just from the type declarations of the variables animals1 and animals2. If the error code does not report the error, that does not mean that a fish into a pile of cats, this belongs to the cat to add food ah, so it is certainly not. ? This is what the covariant expressed by the extends wildcard means.

So what does contravariant mean? Or the animals above:

public static void superFun() { List<Fish> fishList = new ArrayList<>(); fishList.add(new Fish()); List<Animal> animalList = new ArrayList<>(); animalList.add(new Cat()); animalList.add(new Fish()); List<? super Fish> fish1 = fishList; List<? super Fish> fish2 = animalList; fish1.add(new Fish()); Fish fish = fish2.get(0); / / error}Copy the code

Fish1 = fish2; fish2 = fish2; fish2 = fish2; So definitely not. ? This is what the inverse representation of the super wildcard means.

Kotlin also provides two modifiers for covariant and contravariant properties:

  • out: statement covariant;
  • in: Declares the inverter.

They can be used in two ways:

  • The first: andjavaDeclare the same at the place of use;
  • Second: declare it at the definition of the class or interface.

Convert the above Java example to kotlin when declared at use as Java:

fun extendsFun() { val fishList: MutableList<Fish> = ArrayList() fishList.add(Fish()) val catList: MutableList<Cat> = ArrayList() catList.add(Cat()) val animals1: MutableList<out Animal> = fishList val animals2: MutableList<out Animal> = catList animals2. Add (Fish()) val animal1 = animals1[0] val animal2 = animals2[0] animal1.eat() animal2.eat() } fun superFun() { val fishList: MutableList<Fish> = ArrayList() fishList.add(Fish()) val animalList: MutableList<Animal> = ArrayList() animalList.add(Cat()) animalList.add(Fish()) val fish1: MutableList<in Fish> = fishList val fish2: MutableList<in Fish> = animalList fish1.add(Fish()) val fish: Fish = fish2[0]Copy the code

You can see in the Kotlin code that in addition to calling? Extends is replaced with out, which replaces? Super is replaced by in, nothing else has changed, and the result is the same. So what’s the purpose of declaring in and out at the definition of a class or interface?

Suppose we have a generic interface Source

that does not have any methods that take T as an argument, except that the methods return values of type T:

// Java
interface Source<T> {
  T nextT();
}
Copy the code

Then, storing a reference to a Source

instance in a variable of type Source
is extremely safe — there are no consumer-methods to call. But Java doesn’t know this and still forbids it:

// Java void demo(Source<String> strs) { Source<Object> objects = strs; / /!!!!!! Java does not allow //...... }Copy the code

To fix this, we must declare the object of type Source
, but this approach is complicated. There is an easy way to explain this to the compiler in Kotlin. We can annotate the Source type parameter T to ensure that it is only returned (produced) from the Source

member and is never consumed. To do this, we use the out modifier to modify the generic T:

interface Source<out T> { fun nextT(): T } fun demo(strs: Source<String>) { val objects: Source<Any> = STRS // this is ok because T is an out- parameter //... }Copy the code

Remember the definition of covariation at the beginning?

When A ≦ B, f(A) ≦ f(B) is covariant. When A ≦ B, f(B) ≦ f(A) is contravariant.

In other words:

When a type parameter T of class C is declared out, it means that class C is covariant on parameter T; The parameter T can only appear at the output position of class C, not the input position of class C.

Similarly, for the in modifier

When a type parameter T of class C is declared in, it means that class C is contravariant on parameter T; The parameter T can only appear in the input position of class C, not in the output position of class C.

interface Comparable<in T> { operator fun compareTo(other: T): Int } fun demo(x: Comparable<Number>) {x.compareTo(1.0) has type Double, which is a subtype of Number We can assign x to a variable of type Comparable<Double> val y: Comparable<Double> = x // OK! }Copy the code

The summary is as follows:

We have other views can leave a message to exchange learning! Give it a thumbs up!