What this article brings to you is an introduction to the knowledge of Java array covariance and generic invariance (with code). It has certain reference value. Friends in need can refer to it. I hope it will be helpful to you. help.
Variability is a big pitfall of OOP language invariance, and Java's array covariance is one of the old pitfalls. Because I stepped on it recently, I made a note. By the way, let’s also mention the degeneration of paradigms.
Before explaining array covariance, first clarify three related concepts, covariance, invariance and contravariance.
1. Covariance, invariance, contravariance
Suppose, I wrote such a piece of code for a restaurant
class Soup<T> { public void add(T t) {} } class Vegetable { } class Carrot extends Vegetable { }
There is a generic class Soup8742468051c85b06f0a0af9e3e506b5c, which represents soup made with ingredient T. Its method add(T t) represents adding ingredients to the soup. Add ingredient T. The Vegetable class represents vegetables, and the Carrot class represents carrots. Of course, Carrot is a subclass of Vegetable.
Then the question is, what is the relationship between Soupf154463d24280b51e227a9971c008850 and Soup48d44884ab571fbfd69a92f91c198045?
The first reaction is that Soup48d44884ab571fbfd69a92f91c198045 should be a subcategory of Soupf154463d24280b51e227a9971c008850, because carrot soup is obviously a vegetable soup. If that's the case, then take a look at the code below. Among them, Tomato means tomatoes, which is another subclass of Vegetable
Soup<Vegetable> soup = new Soup<Carrot>(); soup.add(new Tomato());
The first sentence is okay, Soup48d44884ab571fbfd69a92f91c198045 is a subclass of Soupf154463d24280b51e227a9971c008850, So you can assign the instance of Soup48d44884ab571fbfd69a92f91c198045 to the variable soup. The second sentence is no problem, because soup is declared as Soupf154463d24280b51e227a9971c008850 type, and its add method receives a parameter of Vegetable type, and Tomato is Vegetable and has the correct type.
However, there is a problem when putting the two sentences together. The actual type of soup is Soup48d44884ab571fbfd69a92f91c198045, and we passed an instance of Tomato to its add method! In other words, if we are making carrot soup with tomatoes, we will definitely not be able to make it. Therefore, although it is logically logical to regard Soup48d44884ab571fbfd69a92f91c198045 as a subclass of Soupf154463d24280b51e227a9971c008850, it is flawed during use.
So, what is the relationship between Soup48d44884ab571fbfd69a92f91c198045 and Soupf154463d24280b51e227a9971c008850? Different languages have different understandings and implementations. To sum up, there are three situations.
(1) If Soup48d44884ab571fbfd69a92f91c198045 is a subclass of Soupf154463d24280b51e227a9971c008850, the generic Soup8742468051c85b06f0a0af9e3e506b5c is said to be covariant
(2) If Soup48d44884ab571fbfd69a92f91c198045 and Soupf154463d24280b51e227a9971c008850 ; are two unrelated classes, then the generic Soup8742468051c85b06f0a0af9e3e506b5c is said to be invariant
(3) If Soup48d44884ab571fbfd69a92f91c198045 is the parent class of Soupf154463d24280b51e227a9971c008850, then the generic Soup8742468051c85b06f0a0af9e3e506b5c is said to be the inverse changing. (However, contravariance is not common)
After understanding the concepts of covariance, invariance and contravariance, let’s look at the implementation of Java. Java's general generics are immutable, which means Soupf154463d24280b51e227a9971c008850 and Soup48d44884ab571fbfd69a92f91c198045 are two unrelated classes, and instances of one class cannot be assigned to variables of the other class. Therefore, the above code that uses tomatoes to make carrot soup actually cannot be compiled at all.
2. Array covariance
In Java, arrays are basic types, not generics, and there is no such thing as Array8742468051c85b06f0a0af9e3e506b5c . But it's very similar to a generic, in that it's a type built from another type. Therefore, arrays must also be considered mutable.
Different from the immutability of generics, Java arrays are covariant. In other words, Carrot[] is a subclass of Vegetable[]. The examples in the previous section have shown that covariance can sometimes cause problems. For example, the following code
Vegetable[] vegetables = new Carrot[10]; vegetables[0] = new Tomato(); // 运行期错误
Because arrays are covariant, the compiler allows Carrot[10] to be assigned to variables of type Vegetable[], so this The code can be compiled successfully. It's only during runtime, when the JVM actually tries to insert a tomato into a pile of carrots, that something big goes wrong. Therefore, the above code will throw an exception of type java.lang.ArrayStoreException during runtime.
Array covariance is one of Java’s famous historical baggage. Be careful when using arrays!
If you replace the array in the example with a List, the situation will be different. Like this
ArrayList<Vegetable> vegetables = new ArrayList<Carrot>(); // 编译期错误 vegetables.add(new Tomato());
ArrayList is a generic class and it is immutable. Therefore, there is no inheritance relationship between ArrayList48d44884ab571fbfd69a92f91c198045 and ArrayListf154463d24280b51e227a9971c008850, and this code will report an error during compilation.
Although both pieces of code will report errors, compile-time errors are usually easier to handle than run-time errors.
3. When generics also want covariance and contravariance
Generics are immutable, but in some scenarios we Still hope it can covariate. For example, there is a young lady who drinks vegetable soup every day to lose weight
class Girl { public void drink(Soup<Vegetable> soup) {} }
我们希望drink方法可以接受各种不同的蔬菜汤,包括Soup48d44884ab571fbfd69a92f91c198045和Soup8ad9f592c4c8b60fa486d2a1b7041744。但受到不变性的限制,它们无法作为drink的参数。
要实现这一点,应该采用一种类似于协变性的写法
public void drink(Soup<? extends Vegetable> soup) {}
意思是,参数soup的类型是泛型类Soup8742468051c85b06f0a0af9e3e506b5c,而T是Vegetable的子类(也包括Vegetable自己)。这时,小姐姐终于可以愉快地喝上胡萝卜汤和西红柿汤了。
但是,这种方法有一个限制。编译器只知道泛型参数是Vegetable的子类,却不知道它具体是什么。所以,所有非null的泛型类型参数均被视为不安全的。说起来很拗口,其实很简单。直接上代码
public void drink(Soup<? extends Vegetable> soup) { soup.add(new Tomato()); // 错误 soup.add(null); // 正确}
方法内的第一句会在编译期报错。因为编译器只知道add方法的参数是Vegetable的子类,却不知道它具体是Carrot、Tomato、或者其他的什么类型。这时,传递一个具体类型的实例一律被视为不安全的。即使soup真的是Soup8ad9f592c4c8b60fa486d2a1b7041744类型也不行,因为soup的具体类型信息是在运行期才能知道的,编译期并不知道。
但是方法内的第二句是正确的。因为参数是null,它可以是任何合法的类型。编译器认为它是安全的。
同样,也有一种类似于逆变的方法
public void drink(Soup<? super Vegetable> soup) {}
这时,Soup8742468051c85b06f0a0af9e3e506b5c中的T必须是Vegetable的父类。
这种情况就不存在上面的限制了,下面的代码毫无问题
public void drink(Soup<? super Vegetable> soup) { soup.add(new Tomato()); }
Tomato是Vegetable的子类,自然也是Vegetable父类的子类。所以,编译期就可以确定类型是安全的。
The above is the detailed content of Introduction to the knowledge of Java array covariance and generic invariance (with code). For more information, please follow other related articles on the PHP Chinese website!