preface

The birth of this article actually comes from guo Shen’s advice to me. It is also a response to the interviewer who asked me before: “What is generic erasure and what problems will it bring?” A supplement to the content of the article, I hope I can correct me if there is any misunderstanding.

What are contravariant and covariant?

From Baidu you can get a unified solution similar to the following text by typing keywords in this way: Java inverse and covariant.

Contravariant and covariant describe the inheritance relationship after type conversion.

Define two types A and B, A is A subclass derived from B (A<=B), f() represents type conversions such as new List();

  1. Covariant: f(A)<=f(B) holds when A<=B. (String -> Object)
  2. Contravariant: f(B)<=f(A) holds when A<=B. (Object -> String)
  3. Invariant: when A<=B, the above two equations are not true

Let’s start by explaining what these words mean.

/ / covariant
List<? extends Fruit> fruit = new ArrayList<Apple>();
/ / inverter
List<? super Apple> fruit = new ArrayList<Fruit>();
/ / the same
List<Apple> fruit = new ArrayList<Apple>();
Copy the code

List

List = new ArrayList() or Fruit[] Fruit = new Apple[10]; It will compile, but the invariant part will not compile why is that?

List

Fruit = new ArrayList

(); You’re going to get a compilation error, in other words ArrayList

and ArrayList

they’re not a family anymore. Contravariant and covariant mechanisms were introduced to allow them to re-establish parent-child relationships. To complete this step, we need to use the question that the interviewer asked me before: “What is generic erasure and what problems does it cause?” Extends and super are already involved to do this.



With that in mind, let’s logically prove the above argument.

Covariance:F (A)<=f(B) is true if A<=B

Definition: if A is A subclass of B, it can complete the transformation from parent to subclass.

You can refer to the downward transition to some extent.

List list = new ArrayList();
List<Fruit> flist = new ArrayList<Apple>(); // To make this form work, we have the code below
List<? extends Fruit> fruitList = new ArrayList<Apple>();
Copy the code

Let’s take a look at the scope of extends:

Extends is a keyword used in generics to introduce covariant mechanisms. The scope of extends is already familiar. Compare the two pieces of code above. As we’ve said the List is not as internal array or done directly as you create an object directly change, similar to the T such a generic wildcards, it is not at compile time for such data transformation, the reason is what articles before I actually have to generic tells the story, and so on to go over again.

Inverter:F (B)<=f(A) is true if A<=B

Definition: if A is A subclass of B, it can transform the subclass to its parent. (Refer to upward transformation to some extent)

List list =(List) new ArrayList();
List<Fruit> fruit = (ArrayList<Fruit>)new ArrayList<Apple>(); // Same compiler error, same with super
List<? super Apple> fruit1 = new ArrayList<Fruit>();
Copy the code

Similarly, in order to solve such problems, Java introduced the super keyword, whose scope is shown as follows:

What is constant, I will not say. I think the reader, after reading the above, has a pretty good idea of what immutable means, which is this code, which can only be equal to itself. ArrayList

and ArrayList

are no longer a family.

List<Apple> list = new ArrayList<Apple>();
Copy the code

What is the function of contravariant and covariant?

Come, come, another knowledge point of repetition. And the question is why do you introduce contravariant and covariant mechanisms?

First, what are the problems with generics when they run? Apparently, generic erasure!

So what exactly does generic erasure look like? If you have read the previous article, you will definitely want to scold me. If you have changed the type into Object, you will eventually convert it back to Object by strong casting.

Let’s review the get() source code for ArrayList.

public E get(int index) {
            rangeCheck(index);
            checkForComodification();
            return ArrayList.this.elementData(offset + index); / / 1 - >
        }
        
E elementData(int index) {
        return (E) elementData[index];
    }
Copy the code

This whole part calls two functions altogether, and the second function, which you clearly cast, why? Because it saves Object instead of Apple.

Why invert the insert operation

By introducing a covariant mechanism, List
fruits = new ArrayList

(); That means that some specific type that inherits from Fruit knows the upper bound, but the lower bound is in a completely unknown state. In this case, the obvious thing is that you can’t do our insert.

List<? extends Fruit> fruits = new ArrayList<Apple>();
fruits.add(new Apple());  // Compile error
fruits.add(new Fruit());  // Compile error
fruits.add(new Object());  // Compile error
Copy the code

Why is that? It’s easy because you forget who you are, but you know the upper bound, which means who your parents are, or who your grandparents are. It’s like a family tree. The person at the top is your once, once… Grandpa and grandma, and we’re just kids at the bottom of the family tree. You know your great-grandma was your elder, but great-grandma doesn’t necessarily know which side of the family you’re from.

So when you create something, you have to be aware of something like List
fruits = new ArrayList

(); He is Apple this time, another person may be Orange, and another person may be Pear, so what will happen? Whether he should save Apple, Orange or Pear in this List, no matter which one is stored, the other one may become the spare tire, in order to avoid such a problem, just don’t save them.

And inverter mechanism is different, he already know the lower bound, but don’t know the upper bound, his children and grandchildren that this time he met, but we know they elders that column, but they have a lot of things we inherited and developed the new things, and their matching degree is no longer the same, you can’t let them to join our new generation, the older The kids and grandkids take everything from you and have their own new stuff (which we don’t even think about anymore), so you can see that the List can add our kids’ new stuff to it and exclude the old ones’ backward ideas.

List<? super Apple> apples = new ArrayList<Fruit>();
apples.add(new Apple());
apples.add(new Jonathan());
apples.add(new Fruit());  // Error compiling
Copy the code

Why use covariant for fetch operations

List<? extends Fruit> fruits = new ArrayList<Apple>();
Apple apple = fruits.get(0);
Jonathan jonathan = fruits.get(0);  // Error compiling
Fruit fruit = fruits.get(0);
Copy the code

Why is it that extends can get data with a ratio greater than or equal to itself? We’ve already said that he determines the upper bound, and if you subclass the data, you’re going to get data mismatches. So there are natural limits on that. Let’s say fruits.get(0) is actually retrieving a subclass B of Apple. If the third line of code runs successfully, there will be a mismatch because you can’t guarantee that subclass Jonathan will be identical to subclass B, but they will be identical to Apple. Of course, for further data security, we generally use the base class to obtain our data, namely Fruit.

List<? super Apple> apples = new ArrayList<Fruit>();
Object jonathan = apples.get(0);
// The rest can only be obtained by force conversion
Copy the code

In the inversion of data acquisition, data information has been lost, why say so? Because we know the lower bound, but the upper bound, it could be Fruit, it could be Object, it could be the base class of everything, but we don’t know who the upper bound is, so the compiler sets it to Object, so it’s not suitable for fetching.

case

In Effective Java, there is a very succinct answer: producer-extends, consumer-super (PECS).

Literally, extends is a restriction on the source of data (producer) and super is a restriction on the inflow of data (consumer). And obviously some of the code that we use a lot also conforms to this rule.

public static <T> void copy(List<? super T> dest, List<? extends T> src) {
        int srcSize = src.size();
        if (srcSize > dest.size())
            throw new IndexOutOfBoundsException("Source does not fit in dest");

        if (srcSize < COPY_THRESHOLD ||
            (src instanceof RandomAccess && dest instanceof RandomAccess)) {
            for (int i=0; i<srcSize; i++)
                dest.set(i, src.get(i));
        } else {
            ListIterator<? super T> di=dest.listIterator();
            ListIterator<? extends T> si=src.listIterator();
            for (int i=0; i<srcSize; i++) { di.next(); di.set(si.next()); }}}Copy the code

The copy() function for Collections is a copy of what you put in List
dest, List
SRC, and SRC data is similar to covariant, a suitable solution to fetch. The comparator dest uses super because it does things like store. This also validates the PECS I mentioned above.

conclusion

At the end of the day, all the reasons for doing this above are actually for data security.

Here is a summary of the formula:

Given data lower bound (super), should be refined storage; Given data upper bound (extends), should be rough to take. (Where refinement refers to the use of subclass or itself, coarse refers to the use of parent class or itself)

Wait! Wait! Wait!!!!!!

Question: Do you have a problem here? Do you feel a little different from PECS? When I say extends means take and super means store, isn’t that the other way around?

In fact, it’s the same. Let’s take our case above. To prove this, Sun engineers are more experienced than I am, this thing must be right, and you can actually see in the case that you get data from SRC and then store the comparison in DES, which suggests a question, should you change this formula to PSCE? No! No! No!!!!!!

A producer is someone who has already made something, and extends fits that requirement. You just take it. And consumers? Obviously someone who can take these things extends does he insert things? If you say yes, you have to write your own code to try, obviously only super to complete ah. That also shows that PECS principle is correct, and the extraction is very refined. I hope the great God can accept my knee.