Inversion and covariance in Java

Look at the following code

Number num = new Integer(1);  
ArrayList<Number> list = new ArrayList<Integer>(); //type mismatch

List<? extends Number> list = new ArrayList<Number>();
list.add(new Integer(1)); //error
list.add(new Float(1.2f));  //error

Some people wonder why the Number object can be instantiated by Integer, but the ArrayList object cannot be instantiated by ArrayList? <? In the list? Extensions Number> declares that its element is a Number or a derived class of Number. Why can't you add Integer and Float? To solve these problems, we need to understand the inversion and covariance in Java and the wildcard usage in generics.

1. inverter and covariant
Before introducing inversion and covariance, the Liskov Substitution Principle (LSP) is introduced.

Liskov Substitution Principle
LSP was proposed by Barbara Liskov in 1987. Its definition is as follows:

All references to the base class (parent class) must be able to transparently use the objects of its subclasses.

LSP contains the following four meanings:

  • The subclass completely owns the methods of the parent class, and the concrete subclass must implement the abstract methods of the parent class.

  • Subclasses can add their own methods.

  • When a subclass overrides or implements a method of a parent class, the formal parameters of the method are more relaxed than those of the parent class.

  • When a subclass overrides or implements a method of a parent class, the return value of the method is more strict than that of the parent class.
    The first two meanings are easy to understand, and the latter two meanings will be explained in detail below. According to LSP, when we instantiate an object, we can instantiate it with its subclasses, such as:

Number num = new Integer(1);
definition
Inversion and covariance are used to describe the inheritance relationship after type transformation. Their definitions: if A and B represent types, f(⋅) represents type conversion, and ≤ represents inheritance relationship (for example, A ≤ B represents that A is A subclass derived from b);

f(⋅) is contravariant. When A ≤ B, f(B) ≤ f(A) is true;
f(⋅) is covariant. When A ≤ B, f(A) ≤ f(B) holds;
f(⋅) is invariant. When A ≤ B, neither of the above two formulas holds, that is, f(A) and f(B) have no inheritance relationship with each other.
Type conversion
Next, let's look at the covariance, inversion, or invariance of common type conversions in Java.

generic paradigm

Let f(A)=ArrayList, then is the inverse, covariant or invariable when f(⋅)? In case of inversion, ArrayList is the parent type of ArrayList; If it is covariant, ArrayList is a subtype of ArrayList; If it is the same, they do not inherit from each other. The error in instantiating the list object with ArrayList in the opening code indicates that the generic type is invariant.

array

Let f(A)=[]A, it is easy to prove that the array is covariant:

Number[] numbers = new Integer[3]; 

method

Method parameters are covariant and return values are inverse:

Through the discussion with the netizen iamzhoug37, it is updated as follows.

Call method result = method(n); According to the Liskov replacement principle, the type of the incoming formal parameter n should be the subtype of the method formal parameter, that is, typeof(n) ≤ typeof(method's parameter); Result should be the base type of the method return value, that is, typeof(methods' return) ≤ typeof(result):

static Number method(Number num) {
	return 1;
}
//Join the Java development exchange sample: 756584822 chat together
Object result = method(new Integer(2)); //correct
Number result = method(new Object()); //error
Integer result = method(new Integer(2)); //error

In Java 1.4, when a subclass override s a parent class method, the types of formal parameters and return values must be consistent with the parent class:

class Super {
    Number method(Number n) { ... }
}

class Sub extends Super {
    @Override 
    Number method(Number n) { ... }
}

Starting from Java 1.5, covariance is allowed to return more specific types when subclasses override parent methods:

class Super {
    Number method(Number n) { ... }
}

class Sub extends Super {
    @Override 
    Integer method(Number n) { ... }
}//Join the Java development exchange sample: 756584822 chat together

2. wildcards in generics
Implementing covariance and inversion of generics
Generics in Java are invariant, but sometimes it is necessary to implement inversion and covariance. What should I do? In this case, wildcards? Came in handy:

<? extends>Realize the covariance of generic types, such as:
List<? extends Number> list = new ArrayList<Integer>();
<? super>Implements the inversion of generic types, such as:
List<? super Number> list = new ArrayList<Object>();

Extensions and super
Why (in the opening code) list<? Extensions number> does the list have compilation errors in add Integer and Float? First, let's look at the implementation of add:

public interface List<E> extends Collection<E> {
	boolean add(E e);
}

When the add method is called, the generic E automatically becomes <? Extensions Number>, which means that the type held by the list is one of the subclasses derived from Number and Number, which contains Integer type but does not specifically refer to Integer type (Integer is like a spare tire!!!), Therefore, a compilation error occurred while adding Integer. To call the add method, you can use the super keyword:

List<? super Number> list = new ArrayList<Object>();
list.add(new Integer(1));
list.add(new Float(1.2f));

Indicates that the type held by the list is a certain type in the base classes of Number and Number, where Integer and Float must be subclasses of this certain type; So the add method can be called correctly. As can be seen from the above example, extensions determines the upper bound of generics, while super determines the lower bound of generics.

PECS

Now the question arises: when do I use extends and when do I use super? Effective Java gives the answer:

PECS: producer-extends, consumer-supe

For example, a simple Stack API:

public class  Stack<E>{
    public Stack();
    public void push(E e):
    public E pop();
    public boolean isEmpty();
}//Join the Java development exchange sample: 756584822 chat together

To implement the pushall (iteratable src) method, stack the src elements one by one:

public void pushAll(Iterable<E> src){
    for(E e : src)
        push(e)
}

Suppose there is an object stack instantiating stack, and src has iteratable and iteratable; A type mismatch error will occur when the pushAll method is called, because generics are immutable in Java, and neither Iterable nor Iterable is a subtype of Iterable. Therefore, it should be changed to

// Wildcard type for parameter that serves as an E producer
public void pushAll(Iterable<? extends E> src) {
    for (E e : src)
        push(e);
}//Join the Java development exchange sample: 756584822 chat together

To implement the popAll(Collection dst) method, take out the elements in the Stack and add them to the dst in turn. If the wildcard is not used for implementation:

// popAll method without wildcard type - deficient!
public void popAll(Collection<E> dst) {
    while (!isEmpty())
        dst.add(pop());   
}//Join the Java development exchange sample: 756584822 chat together

Similarly, suppose that there is an object stack instantiating stack, and dst is Collection; A type mismatch error occurs when the popAll method is called because the Collection is not a subtype of the Collection. Therefore, it should be changed to:

// Wildcard type for parameter that serves as an E consumer
public void popAll(Collection<? super E> dst) {
    while (!isEmpty())
        dst.add(pop());
}//Join the Java development exchange sample: 756584822 chat together

In the above example, when the pushAll method is called, E instances are produced, and when the popAll method is called, dst consumes E instances. Naftalin and Wadler call PECS Get and Put Principle.

java. util. The copy method of collections (JDK1.7) perfectly interprets PECS:

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());
        }
    }
}//Join the Java development exchange sample: 756584822 chat together

PECS summary:

To get data from a generic class, use extends;
To write data to a generic class, use super;
If you want to get and write, you don't need wildcards (that is, neither extends nor super).

Some high-frequency interview questions collected in the latest 2020 collection (all sorted into documents) have a lot of dry goods, including detailed explanations of mysql, netty, spring, threads, spring cloud, jvm, source code, algorithm, etc. there are also detailed learning plans, interview questions sorting, etc. for those who need to obtain these contents, please add Q sample: 756584822

Tags: Java Spring Design Pattern architecture Programming AI

Posted by j_70 on Fri, 03 Jun 2022 12:23:48 +0530