Why do I need to use generic wildcards? --"JAVA Programming Ideas" 56

Keywords: Java

Let's start with an example where an array of export types gives an array of base types:

public class Fruit {}
public class Apple extends Fruit {}
public class RedApple extends Apple {}
public class CovariantArrays {

    public static void main(String[] args) {
        Fruit[] fruits = new Apple[10];
        fruits[0] = new Apple();
        System.out.println("Save Successfully Apple");
        fruits[1] = new RedApple();
        System.out.println("Save Successfully RedApple");
        fruits[2] = new Fruit();
        System.out.println("Save Successfully Fruit");
    }

}
Save Successfully Apple
 Save Successfully RedApple
Exception in thread "main" java.lang.ArrayStoreException: mtn.baymax.charpter15.Fruit
	at mtn.baymax.charpter15.CovariantArrays.main(CovariantArrays.java:16)

Although the above code can be compiled, throwing ArrayStoreException at runtime indicates that we store the wrong type in the array.

The actual reference to fruits arrays is still an array of Apple type. Although the compiler allows you to put any Fruit type and its exported classes into the array, the element type stored in the array is immutable and can only store the Apple type or its subclasses, so Fruit types cannot be stored in fruits.

In fact, the upward transition of arrays is inappropriate here. The scary thing is that even if the storage type is incorrect, it can be compiled and we can only find this error at run time.

But sometimes we want a container of Fruit type to receive all kinds of containers to store Fruit and Fruit export classes. What should we do?

The first thing we think about is using List s as containers, which do type checks on stored elements to enhance the security of your code.

However, a List with an Apple type is not equivalent to a List with a Fruit type. This is because a List with a Fruit type can store Fruit and any of its exported classes, but a List with an Apple type can only store Apple types and their subclasses. The two containers are fundamentally different (imagine you can put an Orange type in an Apple type List?)The following code cannot be compiled.

public class GenericsAndCovariance {

    public void printFruit(ArrayList<Fruit> fruits) {
        for (Fruit fruit : fruits) {
            System.out.println(fruit);
        }
    }

    public static void main(String[] args) {
        ArrayList<Apple> apples = new ArrayList<>();
        GenericsAndCovariance gc = new GenericsAndCovariance();
        //Wrong type, unable to compile
        //gc.printFruit(apples);
    }

}

If you want fruits to receive a List that holds any Fruit export type, you can solve this by using generic wildcards.

public class GenericsAndCovariance {

    public void printFruit(ArrayList<? extends Fruit> fruits) {
        for (Fruit fruit : fruits) {
            System.out.println(fruit);
        }
    }

    public static void main(String[] args) {
        ArrayList<Apple> apples = new ArrayList<>();
        apples.add(new Apple());
        apples.add(new Apple());
        apples.add(new Apple());
        GenericsAndCovariance gc = new GenericsAndCovariance();
        gc.printFruit(apples);
        ArrayList<Fruit> fruits = new ArrayList<>();
        fruits.add(new Fruit());
        fruits.add(new Fruit());
        fruits.add(new Fruit());
        gc.printFruit(fruits);
    }

}

mtn.baymax.charpter15.Apple@1b6d3586
mtn.baymax.charpter15.Apple@4554617c
mtn.baymax.charpter15.Apple@74a14482
mtn.baymax.charpter15.Fruit@1540e19d
mtn.baymax.charpter15.Fruit@677327b6
mtn.baymax.charpter15.Fruit@14ae5a5

ArrayList<?Extds Fruit> fruits indicates that it holds any List that inherits from the Fruit type, so fruits can receive Lists that hold the Frult and Apple types.

Although we can read elements stored in fruits normally, we can't store any elements in fruits, even Object s, why?

    public void printFruit(ArrayList<? extends Fruit> fruits) {
        for (Fruit fruit : fruits) {
            System.out.println(fruit);
        }
        //None of the following three types can be compiled
        //fruits.add(new Fruit());
        //fruits.add(new Apple());
        //fruits.add(new Object());
    }

When any List holding Fruit export classes is assigned to fruits, an upward transition is performed (as in the first example, an array upward transition)However, the type of upgrade here is not a specific type. We only know that it inherits from Fruit, perhaps Fruit, Apple, or Orange, Banana. Without being able to determine the specific type in the List container, the compiler prohibits us from adding any data to the container, but because we know that the type of storage inherits fromFruit, therefore, can safely transition up to Fruit while reading.

This is the end of the sharing. I hope this article will help you. Thank you for lighting up the compliment button below.

If you have any questions, you are welcome to communicate with me. If there are any deficiencies, you are welcome to correct them!

Posted by ziegel on Sun, 26 Sep 2021 10:36:57 -0700