Maison  >  Article  >  Java  >  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)

不言
不言avant
2019-02-23 16:38:462469parcourir

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 { }

Il existe une classe générique Soup8742468051c85b06f0a0af9e3e506b5c, 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 b91f4a86cc9f5d65272341bd46349959 et la soupedb3e77e4438c15955a28e3e6eb750ac0 ?

La première réaction est que Soup48d44884ab571fbfd69a92f91c198045 devrait être une sous-classe de Soupf154463d24280b51e227a9971c008850, 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());

La première phrase est correcte, Soup48d44884ab571fbfd69a92f91c198045 Légume>, afin que vous puissiez attribuer une instance de Soup48d44884ab571fbfd69a92f91c198045 La deuxième phrase ne pose aucun problème, car la soupe est déclarée comme type Soupf154463d24280b51e227a9971c008850 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 Soup48d44884ab571fbfd69a92f91c198045, 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 Soup48d44884ab571fbfd69a92f91c198045 comme une sous-classe de Soupf154463d24280b51e227a9971c008850, son utilisation présente des défauts.

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

(1) Si Soup48d44884ab571fbfd69a92f91c198045 est une sous-classe de Soupf154463d24280b51e227a9971c008850, le générique Soup8742468051c85b06f0a0af9e3e506b5c est dit covariant
(2) Si Soup48d44884ab571fbfd69a92f91c198045 ; sont deux classes indépendantes, alors la classe générique Soup8742468051c85b06f0a0af9e3e506b5c est dite invariante
(3) Si Soup48d44884ab571fbfd69a92f91c198045 est la classe parente de Soupf154463d24280b51e227a9971c008850, alors la classe générique Soup8742468051c85b06f0a0af9e3e506b5c 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 Soupf154463d24280b51e227a9971c008850 et Soup48d44884ab571fbfd69a92f91c198045 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 Array8742468051c85b06f0a0af9e3e506b5c . 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(); // 运行期错误

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());

ArrayList est une classe générique et elle est immuable. Par conséquent, il n’existe aucune relation d’héritage entre ArrayList48d44884ab571fbfd69a92f91c198045 et ArrayListf154463d24280b51e227a9971c008850, 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) {}
}

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

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!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer