Une introduction aux types génériques en Java: covariance et contravariance

Les types

Java est un langage de type statique, ce qui signifie que vous devez d'abord déclarer une variable et son type avant de l'utiliser.

Par exemple: int myInteger = 42;

Entrez les types génériques.

Types génériques

Définition: "Un type générique est une classe ou une interface générique qui est paramétrée sur des types."

Essentiellement, les types génériques vous permettent d'écrire une classe (ou méthode) générale et générique qui fonctionne avec différents types, permettant la réutilisation du code.

Plutôt que de spécifier objqu'il s'agit d'un inttype, ou d'un Stringtype, ou de tout autre type, vous définissez la Boxclasse pour accepter un paramètre de type <; T>. Ensuite, vous pouvez nutiliser T pour représenter ce type générique dans n'importe quelle partie de votre classe.

Maintenant, entrez la covariance et la contravariance.

Covariance et contravariance

Définition

La variance fait référence à la relation entre le sous-typage entre des types plus complexes et le sous-typage entre leurs composants (source).

Une définition facile à retenir (et extrêmement informelle) de la covariance et de la contravariance est:

  • Covariance: accepter les sous-types
  • Contravariance: accepter les supertypes

Tableaux

En Java, les tableaux sont covariants , ce qui a 2 implications.

Premièrement, un tableau de type T[]peut contenir des éléments de type Tet ses sous-types.

Number[] nums = new Number[5];nums[0] = new Integer(1); // Oknums[1] = new Double(2.0); // Ok

Deuxièmement, un tableau de type S[]est un sous-type de T[]if Sest un sous-type de T.

Integer[] intArr = new Integer[5];Number[] numArr = intArr; // Ok

Cependant, il est important de se rappeler que: (1) numArrest une référence de type référence Number[]à «l'objet réel» intArrde «type réel» Integer[].

Par conséquent, la ligne suivante compilera très bien, mais produira un runtime ArrayStoreException(à cause de la pollution du tas):

numArr[0] = 1.23; // Not ok

Il produit une exception d'exécution, car Java sait au moment de l'exécution que «l'objet réel» intArrest en fait un tableau de Integer.

Génériques

Avec les types génériques, Java n'a aucun moyen de connaître au moment de l'exécution les informations de type des paramètres de type, en raison de l'effacement de type. Par conséquent, il ne peut pas protéger contre la pollution du tas au moment de l'exécution.

En tant que tels, les génériques sont invariants.

ArrayList intArrList = new ArrayList();ArrayList numArrList = intArrList; // Not okArrayList anotherIntArrList = intArrList; // Ok

Les paramètres de type doivent correspondre exactement, pour se protéger contre la pollution du tas.

Mais entrez des caractères génériques.

Caractères génériques, covariance et contravariance

Avec les caractères génériques, les génériques peuvent prendre en charge la covariance et la contravariance.

En peaufinant l'exemple précédent, nous obtenons ceci, qui fonctionne!

ArrayList intArrList = new ArrayList();ArrayList numArrList = intArrList; // Ok

Le point d'interrogation "?" fait référence à un caractère générique qui représente un type inconnu. Il peut être inférieur, ce qui limite le type inconnu à un type spécifique ou à son supertype.

Par conséquent, à la ligne 2, se ? super Integertraduit par «tout type qui est un type entier ou son supertype».

Vous pouvez également limiter le caractère générique, qui limite le type inconnu à un type spécifique ou à son sous-type, en utilisant ? extends Integer.

Lecture seule et écriture seule

La covariance et la contravariance produisent des résultats intéressants. Les types covariants sont en lecture seule, tandis que les types contravariants sont en écriture seule.

N'oubliez pas que les types covariants acceptent les sous-types, donc ArrayLister> can contain any object that is either of a Number type or its subtype.

In this example, line 9 works, because we can be certain that whatever we get from the ArrayList can be upcasted to a Number type (because if it extends Number, by definition, it is a Number).

But nums.add() doesn’t work, because we cannot be sure of the “actual type” of the object. All we know is that it must be a Number or its subtypes (e.g. Integer, Double, Long, etc.).

With contravariance, the converse is true.

Line 9 works, because we can be certain that whatever the “actual type” of the object is, it must be Integer or its supertype, and thus accept an Integer object.

But line 10 doesn’t work, because we cannot be sure that we will get an Integer. For instance, nums could be referencing an ArrayList of Objects.

Applications

Therefore, since covariant types are read-only and contravariant types are write-only (loosely speaking), we can derive the following rule of thumb: “Producer extends, consumer super”.

A producer-like object that produces objects of type T can be of type parameter T>, while a consumer-like object that consumes objects oftype T can be of type parameter super T>.

Original text