Maison > Java > javaDidacticiel > Introduction à la connaissance de la covariance des tableaux Java et de l'invariance générique (avec code)

Introduction à la connaissance de la covariance des tableaux Java et de l'invariance générique (avec code)

不言
Libérer: 2019-02-23 16:38:46
avant
2517 Les gens l'ont consulté

Ce que cet article vous apporte est une introduction à la connaissance de la covariance des tableaux Java et de l'invariance générique (avec code). Il a une certaine valeur de référence. Les amis dans le besoin peuvent s'y référer. aide.

La variabilité est un des principaux écueils de l'invariance du langage POO, et la covariance des tableaux de Java est l'un des anciens écueils. Parce que j'ai marché dessus récemment, j'ai pris note. Au passage, évoquons aussi la dégénérescence des paradigmes.

Avant d'expliquer la covariance des tableaux, clarifiez d'abord trois concepts liés, la covariance, l'invariance et la contravariance.

1. Covariance, invariance, contravariance

Supposons que j'aie écrit un tel morceau de code pour un restaurant

class Soup<T> {
    public void add(T t) {}
}
class Vegetable { }
class Carrot extends Vegetable { }
Copier après la connexion

Il existe une classe générique Soup, qui représente une soupe faite avec l'ingrédient T, et sa méthode add(T t) représente Ajouter l'ingrédient T à la soupe. La classe Légumes représente les légumes et la classe Carotte représente les carottes. Bien entendu, la carotte est une sous-classe du légume.

Alors la question est : quelle est la relation entre la soupe et la soupe ?

La première réaction est que Soup devrait être une sous-classe de Soup, car la soupe aux carottes est évidemment une soupe aux légumes. Si tel est le cas, jetez un œil au code ci-dessous. Parmi eux, Tomate signifie tomates, qui est une autre sous-classe de Légume

Soup<Vegetable> soup = new Soup<Carrot>();
soup.add(new Tomato());
Copier après la connexion

La première phrase est correcte, Soup Légume>, afin que vous puissiez attribuer une instance de Soup La deuxième phrase ne pose aucun problème, car la soupe est déclarée comme type Soup et sa méthode d'ajout reçoit un paramètre de type Vegetal, et la tomate est végétale et le type est correct.

Cependant, il y a un problème lorsque les deux phrases sont mises ensemble. Le type réel de soupe est Soup, et nous avons transmis une instance de Tomato à sa méthode add ! En d’autres termes, si nous préparons une soupe de carottes avec des tomates, nous ne pourrons certainement pas la préparer. Par conséquent, bien qu’il soit logique de traiter Soup comme une sous-classe de Soup, son utilisation présente des défauts.

Alors, quelle est la relation entre la soupe et la soupe ? Différentes langues ont des compréhensions et des implémentations différentes. En résumé, il y a trois situations.

(1) Si Soup est une sous-classe de Soup, le générique Soup est dit covariant
(2) Si Soup ; sont deux classes indépendantes, alors la classe générique Soup est dite invariante
(3) Si Soup est la classe parente de Soup, alors la classe générique Soup l'inverse a changé. (Mais la contravariance n'est pas courante)

Comprenez les concepts de covariance, d'invariance et de contravariance, puis examinez l'implémentation en Java. Les génériques généraux de Java sont immuables, ce qui signifie que Soup et Soup sont deux classes indépendantes et que les instances d'une classe ne peuvent pas être affectées aux variables d'une autre classe. Par conséquent, le code ci-dessus qui utilise des tomates pour faire de la soupe aux carottes ne peut pas du tout être compilé.

2. Covariance des tableaux

En Java, les tableaux sont des types de base, pas des génériques, et Array . Mais c'est très similaire à un générique, dans le sens où c'est un type construit à partir d'un autre type. Par conséquent, les tableaux doivent également être considérés comme mutables.

Contrairement à l'immuabilité des génériques, les tableaux Java sont covariants. En d’autres termes, Carrot[] est une sous-classe de Vegetal[]. Les exemples de la section précédente ont montré que la covariance peut parfois poser problème. Par exemple, le code suivant

Vegetable[] vegetables = new Carrot[10];
vegetables[0] = new Tomato(); // 运行期错误
Copier après la connexion

Les tableaux étant covariants, le compilateur permet à Carrot[10] d'être affecté à des variables de type Vegetal[]. Ce code peut donc être compilé avec succès. Ce n'est que pendant l'exécution, lorsque la JVM essaie d'insérer une tomate dans un tas de carottes, que quelque chose d'important se passe mal. Par conséquent, le code ci-dessus lèvera une exception de type java.lang.ArrayStoreException pendant l'exécution.

La covariance des tableaux est l'un des célèbres bagages historiques de Java. Soyez prudent lorsque vous utilisez des tableaux !

Si vous remplacez le tableau de l'exemple par une List, la situation sera différente. Comme ça

ArrayList<Vegetable> vegetables = new ArrayList<Carrot>(); // 编译期错误
vegetables.add(new Tomato());
Copier après la connexion

ArrayList est une classe générique et elle est immuable. Par conséquent, il n’existe aucune relation d’héritage entre ArrayList et ArrayList, et ce code signalera une erreur lors de la compilation.

Bien que les deux morceaux de code signalent des erreurs, les erreurs de compilation sont généralement plus faciles à gérer que les erreurs d'exécution.

3. Quand les génériques veulent aussi la covariance et la contravariance

Les génériques sont immuables, mais dans certains scénarios, nous espérons toujours qu'ils peuvent covarier. Par exemple, il y a une jeune femme qui boit chaque jour de la soupe aux légumes pour perdre du poids

class Girl {
    public void drink(Soup<Vegetable> soup) {}
}
Copier après la connexion

我们希望drink方法可以接受各种不同的蔬菜汤,包括Soup和Soup。但受到不变性的限制,它们无法作为drink的参数。

要实现这一点,应该采用一种类似于协变性的写法

public void drink(Soup<? extends Vegetable> soup) {}
Copier après la connexion

意思是,参数soup的类型是泛型类Soup,而T是Vegetable的子类(也包括Vegetable自己)。这时,小姐姐终于可以愉快地喝上胡萝卜汤和西红柿汤了。

但是,这种方法有一个限制。编译器只知道泛型参数是Vegetable的子类,却不知道它具体是什么。所以,所有非null的泛型类型参数均被视为不安全的。说起来很拗口,其实很简单。直接上代码

public void drink(Soup<? extends Vegetable> soup) {
    soup.add(new Tomato()); // 错误
    soup.add(null); // 正确}
Copier après la connexion

方法内的第一句会在编译期报错。因为编译器只知道add方法的参数是Vegetable的子类,却不知道它具体是Carrot、Tomato、或者其他的什么类型。这时,传递一个具体类型的实例一律被视为不安全的。即使soup真的是Soup类型也不行,因为soup的具体类型信息是在运行期才能知道的,编译期并不知道。

但是方法内的第二句是正确的。因为参数是null,它可以是任何合法的类型。编译器认为它是安全的。

同样,也有一种类似于逆变的方法

public void drink(Soup<? super Vegetable> soup) {}
Copier après la connexion

这时,Soup中的T必须是Vegetable的父类。

这种情况就不存在上面的限制了,下面的代码毫无问题

public void drink(Soup<? super Vegetable> soup) {
    soup.add(new Tomato());
}
Copier après la connexion

Tomato是Vegetable的子类,自然也是Vegetable父类的子类。所以,编译期就可以确定类型是安全的。

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Étiquettes associées:
source:cnblogs.com
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal