Generic note 3-lower bound wildcard

Posted by chrisranjana on Mon, 07 Mar 2022 18:03:47 +0100

The previous section discussed the role of the upper bound wildcard and its use Today we will discuss lower bound wildcards

Lower bound wildcard

When we need to dynamically pass in class objects and their superclass types, due to the erasure nature, the compiler cannot determine whether the passed in objects are superclasses of an Object, but treat them as Object objects When we need a wildcard that enables the compiler to recognize this relationship, we have the concept of lower bound wildcard
Its format is: <? Super class name >
It tells the compiler that the parameter type you want to pass in can only be this class and its superclass type Let's take a look at the following diagram

If we take fruit as the base class (as the class name after super), then fruit and its parent class types are the object types that should be passed in Note that all subclasses of its fruit should also be considered as fruit types So here, only vegetables and their subclasses should not be introduced

Now suppose we have a plate corresponding to a container class. If this plate can only hold fruit, this actually corresponds to the upper bound wildcard But we don't only want it to hold fruit, but also plants. So we can only say that. We hope this plate can hold not only fruit, but also any category containing fruit It seems strange in reality So I think the real meaning of the lower bound wildcard is not what I need to put on this plate, but what I don't want to put on this plate, so it's natural to say: I don't want this plate to put vegetables
Lower bound wildcards provide us with a means to exclude all sibling types of a base class and all sibling types of its ancestors Next, we will show this through an example
Define a fruit class and inherit the plant class It has several subclasses as shown in the figure above, and vegetables are omitted In order to consider whether it can be passed into a container class, there is no need to define its member variables and methods. Although I did write here, you can completely ignore it. Just define an empty type and an empty constructor If you want to see if a container class object can call its member method through an element, you can declare a member method

package Generic presentation;


//Declare a fruit class and some of its subclasses
public class Fruit extends Plants{
    private String color;

    private double sweetness;

    private boolean isSmooth;

    public Fruit(String color, double sweetness, boolean isSmooth) {
        this.color = color;
        this.sweetness = sweetness;
        this.isSmooth = isSmooth;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public double getSweetness() {
        return sweetness;
    }

    public void setSweetness(double sweetness) {
        this.sweetness = sweetness;
    }

    public boolean isSmooth() {
        return isSmooth;
    }

    public void setSmooth(boolean smooth) {
        isSmooth = smooth;
    }
}


class Strawberry extends Fruit{
    public Strawberry(String color, double sweetness, boolean isSmooth) {
        super(color, sweetness, isSmooth);
    }
}

class Banana extends Fruit{
    public Banana(String color, double sweetness, boolean isSmooth) {
        super(color, sweetness, isSmooth);
    }
}

class NorthBanana extends Banana{
    public NorthBanana(String color, double sweetness, boolean isSmooth) {
        super(color, sweetness, isSmooth);
    }
}

class SouthBanana extends Banana{
    public SouthBanana(String color, double sweetness, boolean isSmooth){super(color, sweetness, isSmooth);}
}

class XinjiangBanana extends NorthBanana{
    public XinjiangBanana(String color, double sweetness, boolean isSmooth) {
        super(color, sweetness, isSmooth);
    }
}

class HainanBanana extends SouthBanana{
    public HainanBanana(String color, double sweetness, boolean isSmooth) {
        super(color, sweetness, isSmooth);
    }
}

In the main method, if we take the northern banana as the base class, we declare a container class object, modify the generic with lower bound wildcards, and then we add some objects to it to observe the results

From the picture, we can see that except northern banana and its subclass, adding other types of fruits will compile and report errors This seems strange. Does it violate the principle of lower bound wildcard, that is, it cannot load the parent type of base class type? In fact, this is still the "pot" of erasure
After the developers of sun company introduced generics into java5, they hope that the third-party libraries in the future will adopt generics to declare, construct and implement But for the class library before Java 5, it will inevitably face the dilemma of rewriting, but it will take some time It is impossible for users to stop using these class libraries for rewriting Developers have taken such a compromise that generics can be used across classes To put it bluntly, when the past classes and the current generic class library are used with each other, the past classes will not recognize generics. For them, they are still some classes without type constraints. Although this greatly limits the ability of generics, it ensures maintainability. Such a feature is called the erasure property of generics
Because of this feature, the compiler is often confused. When it can't specify what type of parameter you want to pass in, it won't allow you to insert it at will for safety Here, the compiler knows to allow the base class and its parent class to pass into the container through the lower bound wildcard, but does not know the specific type of the object passed into the container In this way, you cannot allocate memory for it, and naturally you cannot execute code Why is it allowed to insert subtypes? We said that subtypes are also parent types, that is, bananas are a kind of fruit, which is right, but fruits are not necessarily bananas. When we insert a subtype of a base class, the compiler can recognize that it is a subtype. Although it is impossible to know exactly which seed type it is due to erasure, it must be a base class type, Then allocate the memory space of a base class object Subclasses inherit all the stack information of the parent class, so you don't have to worry about unnecessary variables or addresses
Container objects decorated with lower bound wildcards cannot directly call get methods, which is just the opposite of upper bound wildcards Unless you point to it with a reference of type Object, that's what the compiler thinks But it makes no sense Because you still can't know what type of Object it is, and all its methods, including inherited methods or unique methods, can't be called, except for several methods of Object object It has completely lost all its identity information The reason for this is that when the get() method is executed, the compiler only knows that the obtained element is the base class type or its superclass type, but does not know which specific type it should be. Unlike the practice of upper bound wildcards, it can not be converted downward as a base class type, If you type up, you can't determine whether this type is really the superclass type of the type you get, unless you always convert up to Object type This is why only the Object type reference can point to the Object obtained by the get() method
Based on the same consideration, if the data cannot be obtained, the storage of this data will be meaningless So we must find a way to make the data stored by using the lower bound wildcard visible to us We adopt the principle of reflection Call the getClass() method of Object to get the specific type of Object Then output this type (I don't know how to directly use the returned result of getClass, so I can only output it first). After understanding this result, you can call the unique method or inherited method of the Object by using forced type conversion
Please observe the following code carefully:

package Generic presentation;

import java.util.ArrayList;
import java.util.List;

public class GenericTest2 {
    public static void main(String[] args) {
        //2. Use lower bound wildcard? super is the parent class of all elements in the list or banner
        List<? super NorthBanana> list = new ArrayList<>();

        //2.1 correct usage of lower bound wildcard: you can use the add method, but it should comply with the generic requirements
        list.add(new XinjiangBanana("green", 0.3, true));

        list.add(new NorthBanana("green", 0.3, true));

        //2.2 wrong usage of lower bound wildcard: add the parent class of XinjiangBanana, which cannot be compiled
//        list.add(new Banana("green", 0.3, true));

        //2.3 because the parent class of the returned result cannot be guaranteed, it can only be transformed upward to the highest class Object
//        Fruit f1 = list.get(0);
//
//        NorthBanana nb1 = list.get(0);

        Object o1 = list.get(0);

        Object o2 = list.get(1);

//        o1.getColor();// Direct invocation is definitely not possible, because polymorphism does not allow parent objects to call subclass specific methods

        System.out.println(list.get(0).getClass());

        System.out.println(o1.equals(list.get(0)));

        System.out.println(list.get(1).getClass());

        Class cl = list.get(0).getClass();

        System.out.println(o2.equals(list.get(0)));

        XinjiangBanana xb = (XinjiangBanana)o1;
        System.out.println(xb.getColor());
    }
}

Topics: Java Polymorphism compiler