Java basics: understanding of super and extends in generic <? super T>

The article combines Zhihu’s answers and his own understanding to organize. But the content belongs to the author. Original link: click to jump

<? extends T>And <? super T>is Java generics wildcards and boundary concepts.

<? extends T> : 是指 上界通配符
<? super T> : 是指 下届通配符

1. Why use wildcards and boundaries?

In the process of using generics, there is often a very awkward situation. For example, we have a Fruitclass and its derived Appleclasses.

class Fruit {
    
     } 
class Apple extends Fruit {
    
     }

Then there is the simplest container: the Plate class, a generic " thing " can be placed on the plate . We can do the simplest "put" and "take" actions on this thing: set() and get() methods .

class Plate<T> {
    
    

    private T item;

    public Plate(T t) {
    
    
        item = t;
    }

    public void set(T t) {
    
    
        item = t;
    }

    public T get() {
    
    
        return item;
    }
}

Now define a "fruit plate", logically, the fruit plate can of course contain apples.

Plate<Fruit> p = new Plate<>(new Apple());

But in fact the Java compiler does not allow this operation. It will report an error, "Plate with apples" cannot be converted into "Plate with fruits".

This is not in line with normal logic.

But the logic determined by the compiler is this:

Apple IS-Afruit
loaded apple dish NOT-IS-Aholding fruit plate

Therefore, even if there is an inheritance relationship between the things in the container, there is no inheritance relationship between the containers.

So we can not put Plate<Apple>references passed to Plate<Fruit>.

To make it more comfortable to use generics, Sun's big head who came up with <? extends T>and <? super T>the way to make a relationship between "fruit plate" and "apple dishes."

2. What are wildcards?

When using a generic class, you can specify a specific type. For example, List declares that the specific type is String;

You can also use wildcards? to indicate unknown types.For example, List<?> declares that the elements contained in the List are unknown.

The wildcard represents a set of types, but the specific type is unknown. List<?> declares that all types are possible. But List<?> is not equivalent to List

List actually determines that the List contains Object and its subclasses, which can be referenced by Object when using it. The type of elements contained in List<?> is uncertain. It may contain String, It may also be Integer. If it contains String, it would be wrong to add an element of type Integer. Because the type is unknown, you cannot create a new ArrayList object through the new ArrayList<?>() method. Because of the compiler It is impossible to know the specific type, but for the elements in List<?>, Object can always be used to reference, because although the type is unknown, it must be Object and its subclasses.

Consider the following code:

public void wildcard(List<?> list) {
    
    
    list.add(1);// 编译错误 
}  

As shown above, when trying to operate a generic class with wildcards, a compilation error will always occur. The reason is that the type represented by the wildcard is unknown.

This is the first sentence in the three-sentence summary of Java Generic Wildcards (PECS):? Cannot add elements, only as consumers.

Because the elements in List<?> can only be referenced by Object, it is not very convenient in some cases. In these cases, upper and lower bounds can be used to limit the range of unknown types. For example, List<? extends Number> explains List The element types that may be included are Number and its subclasses, and List<? super Number> means that List contains Number and its parent classes. When the upper bound is introduced, the upper bound class can be used when using the type For example, when accessing List<? extends Number>, you can use methods such as intValue of the Number class.

3. What is the upper bound?

The following code is the "upper bound wildcard":

Plate <? extends Fruit>

Translated into adult words is: a plate that can put fruits and everything that is derived from fruits.

To be more straightforward: a plate that can put any fruit. This is closer to our human logic.

Plate <? extends Fruit>And Plate <Apple>the biggest difference is Plate <? extends Fruit>that Plate <Apple>the base class.

The direct benefit is: we can use "apple plate" to assign value to fruit plate.

Plate<? extends Fruit> p = new Plate<>(new Apple());

If we expand on the example of Fruit and Apple, food is divided into fruit and meat. Fruits include apples and bananas, meats include pork and beef, and apples have two types of green apples and red apples.

// lev 1
class Food {
    
      }

// lev 2
class Fruit extends  Food {
    
      }
class Meat extends Food {
    
      }

// lev 3
class Apple extends Fruit {
    
      }
class Banana extends Fruit {
    
      }
class Pork extends Fruit {
    
      }
class Beef extends Fruit {
    
      }

// lev 4
class RedApple extends Apple {
    
      }
class GreenApple extends Apple {
    
      }

In this system, the upper bound wildcard " Plate <? extends Fruit>" covers the blue area in the figure below.

Insert picture description here

4. What is the lower bound?

Correspondingly, "the lower bound wildcard"

Plate <? super Fruit> 

It expresses the opposite concept: a plate that can hold fruit and everything is the base of fruit.

Plate <? super Fruit> is the base class of Plate, but not the base class of Plate. Corresponding to the example just now, Plate <? super Fruit> covers the red area in the figure below.

Insert picture description here

5. Side effects of upper and lower bound wildcards

Boundaries make it easier to convert between different Java generics. But don't forget that such conversion also has certain side effects. That is , some functions of the container may fail .

Still taking the previous Plateexample, we can do two things on the plate, set() new things on the plate, and get () things from the plate.

class Plate<T>{
    
    
    private T item;
    public Plate(T t){
    
    item=t;}
    public void set(T t){
    
    item=t;}
    public T get(){
    
    return item;}
}

5.1 The upper bound <? extends Fruit> cannot be stored in, only taken out.

(1) <? extends Fruit> will invalidate the set() method of putting things on the plate, but the get() method of fetching things is still valid
(2) The extracted things can only be stored in Fruit or its base class. Sculpt upwards.

For example, in the following example, the two set() methods, insert Appleand Fruitboth report errors.

Plate<? extends Fruit> p=new Plate<Apple>(new Apple());
    
//不能存入任何元素
p.set(new Fruit());    //Error
p.set(new Apple());    //Error
 
//读取出来的东西只能存放在Fruit或它的基类里。
Fruit newFruit1=p.get();
Object newFruit2=p.get();
Apple newFruit3=p.get();    //Error

The compiler only knows that the container is Fruit or its derived class, but it doesn't know what type it is, so when it is taken out, it must be shaped upward as a base class.

It could be Fruit? It could be Apple? It could also be Banana, RedApple, GreenApple? After the compiler used Plate to assign values ​​later, the plate was not marked with "Apple". Instead, it is marked with a placeholder: capture#1 to indicate the capture of a Fruit or a subcategory of Fruit. I don’t know what the specific category is, and the code is capture#1.

Then, whether you want to insert Apple or Meat or Fruit compilers into it, I don't know if it can match this capture#1, so it is not allowed.

So the difference between the wildcard <?> and the type parameter is that all Ts represent the same type to the compiler.

For example, in the following generic method, all three Ts refer to the same type, either String or Integer...

public <T> List<T> fill(T... t);

But the wildcard <?> has no such restriction. Plate<?> simply means: there is something on the plate, and I don’t know what it is.

5.2 The lower bound <? super T> does not affect storing in, but taking out can only be placed in the Object object

(1) Using the lower bound <? super Fruit> will partially invalidate the get() method of taking things from the plate, and can only be stored in the Object object.

Because the prescribed lower bound is not clear about the upper bound, it can only be placed in the most fundamental base class Object.

(2) The set() method is normal.

Plate<? super Fruit> p=new Plate<Fruit>(new Fruit());
 
//存入元素正常
p.set(new Fruit());
p.set(new Apple());
 
//读取出来的东西只能存放在Object类里。
Apple newFruit3=p.get();    //Error
Fruit newFruit1=p.get();    //Error
Object newFruit2=p.get();

Because the lower bound specifies the lower limit of the minimum granularity of the element, it actually relaxes the type control of the container element.

Since the element is the base class of Fruit, it can be stored in a smaller granularity than Fruit.

But reading the elements out is laborious, and only the base Object objects of all classes can be loaded. But in this case, the type information of the elements is all lost.

6. PECS principles

Finally, let’s take a look at what is the PECS (Producer Extends Consumer Super) principle, which is well understood.

  • Producer Extends Producers use Extends to determine the upper bound and put things inside to produce
  • Consumer Super Consumers use Super to determine the lower bound and take things out to consume

(1). It is suitable to use the upper bound Extends to read the content frequently, that is, the return type limit that extends can be used for, and cannot be used for the parameter type limit.
(2). It is suitable for the lower bound Super to be used for frequent insertion. It is limited by parameter type and cannot be used for return type limitation.
(3). Wildcards with super supertype qualification can be written to generic objects, wildcards with extends subtype qualification can be read from generic objects

Guess you like

Origin blog.csdn.net/gaolh89/article/details/108762731