Heim  >  Artikel  >  Java  >  Einführung in das Wissen über Java-Array-Kovarianz und generische Invarianz (mit Code)

Einführung in das Wissen über Java-Array-Kovarianz und generische Invarianz (mit Code)

不言
不言nach vorne
2019-02-23 16:38:462474Durchsuche

Dieser Artikel bietet Ihnen eine Einführung in das Wissen über Java-Array-Kovarianz und generische Invarianz (mit Code). Ich hoffe, dass er für Sie hilfreich ist. helfen.

Variabilität ist eine große Falle der OOP-Sprachinvarianz, und die Array-Kovarianz von Java ist eine der alten Fallstricke. Da ich kürzlich darauf getreten bin, habe ich mir eine Notiz gemacht. Lassen Sie uns übrigens auch die Degeneration von Paradigmen erwähnen.

Bevor Sie die Array-Kovarianz erklären, klären Sie zunächst drei verwandte Konzepte: Kovarianz, Invarianz und Kontravarianz.

1. Kovarianz, Invarianz, Kontravarianz

Angenommen, ich habe so einen Code für ein Restaurant geschrieben

class Soup<T> {
    public void add(T t) {}
}
class Vegetable { }
class Carrot extends Vegetable { }

Es gibt eine generische Klasse Soup8742468051c85b06f0a0af9e3e506b5c, die eine mit Zutat T zubereitete Suppe darstellt, und ihre Methode add(T t) stellt das Hinzufügen zur Suppe Add dar Zutat T. Die Gemüseklasse repräsentiert Gemüse und die Carrot-Klasse repräsentiert Karotten. Natürlich ist Karotte eine Unterklasse von Gemüse.

Dann stellt sich die Frage: Welche Beziehung besteht zwischen Suppe1bd23438c4c44199cc9928ebf5cacfcc und Suppea723981900754c7863a93f779691a4a3?

Die erste Reaktion ist, dass Suppea723981900754c7863a93f779691a4a3 eine Unterklasse von Suppe1bd23438c4c44199cc9928ebf5cacfcc sein sollte, da Karottensuppe offensichtlich eine Gemüsesuppe ist. Wenn das der Fall ist, schauen Sie sich den folgenden Code an. Tomate bedeutet Tomaten, eine weitere Unterklasse von Gemüse

Soup<Vegetable> soup = new Soup<Carrot>();
soup.add(new Tomato());

Der erste Satz ist in Ordnung, Suppea723981900754c7863a93f779691a4a3 ist eine Unterklasse von Suppe1bd23438c4c44199cc9928ebf5cacfcc kann der Variablen Suppe die Instanz von Soup48d44884ab571fbfd69a92f91c198045 zuweisen. Der zweite Satz ist kein Problem, da Suppe als Soupf154463d24280b51e227a9971c008850 deklariert ist und ihre Add-Methode einen Parameter vom Gemüsetyp erhält und Tomato Gemüse ist und den richtigen Typ hat.

Allerdings gibt es ein Problem, wenn die beiden Sätze zusammengesetzt werden. Die eigentliche Suppensorte ist Soup48d44884ab571fbfd69a92f91c198045, und wir haben eine Instanz von Tomato an die Add-Methode übergeben! Mit anderen Worten: Wenn wir Karottensuppe mit Tomaten zubereiten, werden wir sie definitiv nicht zubereiten können. Obwohl es logisch ist, Soup48d44884ab571fbfd69a92f91c198045 als Unterklasse von Soupf154463d24280b51e227a9971c008850 zu behandeln, ist dies bei der Verwendung fehlerhaft.

Was ist also die Beziehung zwischen Suppea723981900754c7863a93f779691a4a3 und Suppe1bd23438c4c44199cc9928ebf5cacfcc? Unterschiedliche Sprachen haben unterschiedliche Verständnisse und Implementierungen. Zusammenfassend gibt es drei Situationen.

(1) Wenn Soup48d44884ab571fbfd69a92f91c198045 eine Unterklasse von Soupf154463d24280b51e227a9971c008850 ist, wird die generische Suppe8742468051c85b06f0a0af9e3e506b5c als kovariante bezeichnet.
(2) Wenn Soup48d44884ab571fbfd69a92f91c198045 ; sind zwei nicht verwandte Klassen, dann heißt die generische Soup8742468051c85b06f0a0af9e3e506b5c (3) Wenn Soup48d44884ab571fbfd69a92f91c198045 die übergeordnete Klasse von Soup8742468051c85b06f0a0af9e3e506b5c das Gegenteil Geändert. (Aber Kontravarianz ist nicht üblich)

Verstehen Sie die Konzepte von Kovarianz, Invarianz und Kontravarianz und schauen Sie sich dann die Implementierung in Java an. Die allgemeinen Generika von Java sind unveränderlich, was bedeutet, dass Soupf154463d24280b51e227a9971c008850 zwei nicht verwandte Klassen sind und Instanzen einer Klasse nicht Variablen der anderen Klasse zugewiesen werden können. Daher kann der obige Code, der Tomaten zur Herstellung von Karottensuppe verwendet, überhaupt nicht kompiliert werden.

2. Array-Kovarianz

In Java sind Arrays Grundtypen, keine Generika, und es gibt kein Array8742468051c85b06f0a0af9e3e506b5c . Aber es ist einem generischen Typ sehr ähnlich, da es sich um einen Typ handelt, der aus einem anderen Typ erstellt wurde. Daher müssen Arrays auch als veränderbar betrachtet werden.

Im Gegensatz zur Unveränderlichkeit von Generika sind Java-Arrays

kovariant. Mit anderen Worten, Carrot[] ist eine Unterklasse von Gemüse[]. Die Beispiele im vorherigen Abschnitt haben gezeigt, dass Kovarianz manchmal Probleme verursachen kann. Zum Beispiel der folgende Code

Vegetable[] vegetables = new Carrot[10];
vegetables[0] = new Tomato(); // 运行期错误

Da Arrays kovariant sind, ermöglicht der Compiler die Zuweisung von Carrot[10] zu Variablen vom Typ Gemüse[], also dies Der Code kann erfolgreich kompiliert werden. Erst zur Laufzeit, wenn die JVM tatsächlich versucht, eine Tomate in einen Karottenhaufen zu stecken, geht etwas Großes schief. Daher löst der obige Code zur Laufzeit eine Ausnahme vom Typ java.lang.ArrayStoreException aus.

Array-Kovarianz ist eines der berühmtesten historischen Probleme Javas. Seien Sie vorsichtig bei der Verwendung von Arrays!

Wenn Sie das Array im Beispiel durch eine Liste ersetzen, sieht die Situation anders aus. So

ArrayList<Vegetable> vegetables = new ArrayList<Carrot>(); // 编译期错误
vegetables.add(new Tomato());

ArrayList ist eine generische Klasse und unveränderlich. Daher besteht keine Vererbungsbeziehung zwischen ArrayList48d44884ab571fbfd69a92f91c198045 und ArrayListf154463d24280b51e227a9971c008850 und dieser Code meldet einen Fehler während der Kompilierung.

Obwohl beide Codeteile Fehler melden, sind Kompilierzeitfehler normalerweise einfacher zu behandeln als Laufzeitfehler.

3. Wenn Generika auch Kovarianz und Kontravarianz wollen

Generika sind unveränderlich, aber in einigen Szenarien hoffen wir immer noch, dass es kovariieren kann. Es gibt zum Beispiel eine junge Dame, die jeden Tag Gemüsesuppe trinkt, um abzunehmen

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父类的子类。所以,编译期就可以确定类型是安全的。

Das obige ist der detaillierte Inhalt vonEinführung in das Wissen über Java-Array-Kovarianz und generische Invarianz (mit Code). Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:cnblogs.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen