Dépendance de type en Java, partie 1
Comprendre la compatibilité des types est fondamental pour écrire de bons programmes Java, mais l'interaction des écarts entre les éléments du langage Java peut sembler très académique aux non-initiés. Cet article s'adresse aux développeurs de logiciels prêts à relever le défi! La partie 1 révèle les relations covariantes et contravariantes entre les éléments plus simples tels que les types de tableaux et les types génériques, ainsi que l'élément spécial du langage Java, le caractère générique. La partie 2 explore la dépendance et la variance de type dans les exemples d'API courants et dans les expressions lambda.
télécharger Télécharger le source Obtenez le code source de cet article, "Dépendance de type en Java, partie 1." Créé pour JavaWorld par le Dr Andreas Solymosi.Concepts et terminologie
Avant d'entrer dans les relations de covariance et de contravariance entre divers éléments du langage Java, assurons-nous que nous avons un cadre conceptuel partagé.
Compatibilité
Dans la programmation orientée objet, la compatibilité fait référence à une relation dirigée entre les types, comme le montre la figure 1.
Andreas SolymosiOn dit que deux types sont compatibles en Java s'il est possible de transférer des données entre les variables des types. Le transfert de données est possible si le compilateur l'accepte et se fait par affectation ou passage de paramètres. A titre d'exemple, short
est compatible avec int
car l'affectation intVariable = shortVariable;
est possible. Mais boolean
n'est pas compatible avec int
car l'affectation intVariable = booleanVariable;
n'est pas possible; le compilateur ne l'acceptera pas.
Parce que la compatibilité est une relation dirigée, elle est parfois compatible avec mais n'est pas compatible avec , ou pas de la même manière. Nous verrons cela plus loin lorsque nous aborderons la compatibilité explicite ou implicite.T1
T2
T2
T1
Ce qui compte, c'est que la compatibilité entre les types de référence n'est possible que dans une hiérarchie de types. Tous les types de classe sont compatibles avec Object
, par exemple, car toutes les classes héritent implicitement de Object
. Integer
n'est pas compatible avec Float
, cependant, car Float
n'est pas une superclasse de Integer
. Integer
est compatible avec Number
, car Number
est une superclasse (abstraite) de Integer
. Comme ils sont situés dans la même hiérarchie de types, le compilateur accepte l'affectation numberReference = integerReference;
.
On parle de compatibilité implicite ou explicite , selon que la compatibilité doit être marquée explicitement ou non. Par exemple, short est implicitement compatible avec int
(comme indiqué ci-dessus) mais pas l'inverse: l'affectation shortVariable = intVariable;
n'est pas possible. Cependant, short est explicitement compatible avec int
, car l'affectation shortVariable = (short)intVariable;
est possible. Ici, nous devons marquer la compatibilité par diffusion , également appelée conversion de type.
De même, parmi les types de référence: integerReference = numberReference;
n'est pas acceptable, seul integerReference = (Integer) numberReference;
serait accepté. Par conséquent, Integer
est implicitement compatible avec, Number
mais Number
uniquement explicitement compatible avec Integer
.
Dépendance
Un type peut dépendre d'autres types. Par exemple, le type de tableau int[]
dépend du type primitif int
. De même, le type générique ArrayList
dépend du type Customer
. Les méthodes peuvent également être dépendantes du type, selon les types de leurs paramètres. Par exemple, la méthode void increment(Integer i)
; dépend du type Integer
. Certaines méthodes (comme certains types génériques) dépendent de plusieurs types - telles que les méthodes ayant plus d'un paramètre.
Covariance et contravariance
La covariance et la contravariance déterminent la compatibilité en fonction des types. Dans les deux cas, la variance est une relation dirigée. La covariance peut être traduite par «différent dans le même sens», ou avec-différent , alors que contravariance signifie «différent dans la direction opposée» ou contre-différent . Les types covariants et contravariants ne sont pas les mêmes, mais il existe une corrélation entre eux. Les noms impliquent la direction de la corrélation.
Ainsi, la covariance signifie que la compatibilité de deux types implique la compatibilité des types qui en dépendent. Étant donné la compatibilité des types, on suppose que les types dépendants sont covariants, comme le montre la figure 2.
Andreas SolymosiLa compatibilité de à implique la compatibilité de ) à ). Le type dépendant est appelé covariant ; ou plus précisément, ) est covariant à ).T1
T2
A(T1
A(T2
A(T)
A(T1
A(T2
Pour un autre exemple: parce que l'affectation numberArray = integerArray;
est possible (en Java, au moins), les types de tableaux Integer[]
et Number[]
sont covariants. Donc, nous pouvons dire que Integer[]
c'est implicitement covariant à Number[]
. Et bien que l'inverse ne soit pas vrai - l'affectation integerArray = numberArray;
n'est pas possible - l'affectation avec type casting ( integerArray = (Integer[])numberArray;
) est possible; par conséquent, disons-nous, Number[]
est explicitement covariant à Integer[]
.
Pour résumer: Integer
est implicitement compatible avec Number
, donc Integer[]
est implicitement covariant avec Number[]
, et Number[]
est explicitement covariant avec Integer[]
. La figure 3 illustre.
De manière générale, on peut dire que les types de tableaux sont covariants en Java. Nous examinerons des exemples de covariance entre les types génériques plus loin dans l'article.
Contravariance
Comme la covariance, la contravariance est une relation dirigée . Alors que la covariance signifie avec-différent , la contravariance signifie contre-différent . Comme je l'ai mentionné précédemment, les noms expriment le sens de la corrélation . Il est également important de noter que la variance n'est pas un attribut des types en général, mais uniquement des types dépendants (tels que les tableaux et les types génériques, ainsi que des méthodes, dont je parlerai dans la partie 2).
Un type dépendant comme A(T)
est appelé contravariant si la compatibilité de à implique la compatibilité de ) à ). La figure 4 illustre.T1
T2
A(T2
A(T1
A language element (type or method) A(T)
depending on T
is covariant if the compatibility of T1
to T2
implies the compatibility of A(T1
) to A(T2
). If the compatibility of T1
to T2
implies the compatibility of A(T2
) to A(T1
), then the type A(T)
is contravariant. If the compatibility of T1
between T2
does not imply any compatibility between A(T1
) and A(T2
), then A(T)
is invariant.
Array types in Java are not implicitly contravariant, but they can be explicitly contravariant , just like generic types. I'll offer some examples later in the article.
Éléments dépendant du type: méthodes et types
En Java, les méthodes, les types de tableaux et les types génériques (paramétrés) sont les éléments dépendants du type. Les méthodes dépendent des types de leurs paramètres. Un type de réseau, T[]
est fonction des types d'éléments, T
. Un type générique G
dépend de son paramètre de type T
. La figure 5 illustre.
Cet article se concentre principalement sur la compatibilité des types, bien que j'aborde la compatibilité entre les méthodes vers la fin de la partie 2.
Compatibilité de type implicite et explicite
Earlier, you saw the type T1
being implicitly (or explicitly) compatible to T2
. This is only true if the assignment of a variable of type T1
to a variable of type T2
is allowed without (or with) tagging. Type casting is the most frequent way to tag explicit compatibility:
variableOfTypeT2 = variableOfTypeT1; // implicit compatible variableOfTypeT2 = (T2)variableOfTypeT1; // explicit compatible
For example, int
is implicitly compatible to long
and explicitly compatible to short
:
int intVariable = 5; long longVariable = intVariable; // implicit compatible short shortVariable = (short)intVariable; // explicit compatible
Implicit and explicit compatibility exists not only in assignments, but also in passing parameters from a method call to a method definition and back. Together with input parameters, this means also passing a function result, which you would do as an output parameter.
Note that boolean
isn't compatible to any other type, nor can a primitive and a reference type ever be compatible.
Method parameters
We say, a method reads input parameters and writes output parameters. Parameters of primitive types are always input parameters. A return value of a function is always an output parameter. Parameters of reference types can be both: if the method changes the reference (or a primitive parameter), the change remains within the method (meaning it isn't visible outside the method after the call--this is known as call by value). If the method changes the referred object, however, the change remains after being returned from the method--this is known as call by reference.
A (reference) subtype is implicitly compatible to its supertype, and a supertype is explicitly compatible to its subtype. This means that reference types are compatible only within their hierarchy branch--upward implicitly and downward explicitly:
referenceOfSuperType = referenceOfSubType; // implicit compatible referenceOfSubType = (SubType)referenceOfSuperType; // explicit compatible
The Java compiler typically allows implicit compatibility for an assignment only if there is no danger of losing information at runtime between the different types. (Note, however, that this rule isn't valid for losing precision, such as in an assignment from int
to float.) For example, int
is implicitly compatible to long
because a long
variable holds every int
value. In contrast, a short
variable does not hold any int
values; thus, only explicit compatibility is allowed between these elements.
Notez que la compatibilité implicite de la figure 6 suppose que la relation est transitive : short
est compatible avec long
.
Semblable à ce que vous voyez sur la figure 6, il est toujours possible d'attribuer une référence d'un sous int
- type à une référence d'un supertype. Gardez à l'esprit que la même affectation dans l'autre sens pourrait lancer un ClassCastException
, cependant, le compilateur Java ne l'autorise qu'avec la conversion de type.
Covariance et contravariance pour les types de tableaux
En Java, certains types de tableaux sont covariants et / ou contravariants. Dans le cas de la covariance, cela signifie que si T
est compatible avec U
, alors T[]
est également compatible avec U[]
. En cas de contravariance, cela signifie qu'il U[]
est compatible avec T[]
. Les tableaux de types primitifs sont invariants en Java:
longArray = intArray; // type error shortArray = (short[])intArray; // type error
Les tableaux de types référence sont cependant implicitement covariants et explicitement contravariants :
SuperType[] superArray; SubType[] subArray; ... superArray = subArray; // implicit covariant subArray = (SubType[])superArray; // explicit contravariant
Andreas Solymosi
Figure 7. Covariance implicite pour les tableaux
Ce que cela signifie, en pratique, est qu'une affectation de composants de tableau pourrait être lancée ArrayStoreException
lors de l'exécution. Si une référence de tableau fait SuperType
référence à un objet de tableau de SubType
, et qu'un de ses composants est alors affecté à un SuperType
objet, alors:
superArray[1] = new SuperType(); // throws ArrayStoreException
This is sometimes called the covariance problem. The true problem is not so much the exception (which could be avoided with programming discipline), but that the virtual machine must check every assignment in an array element at runtime. This puts Java at an efficiency disadvantage against languages without covariance (where a compatible assignment for array references is prohibited) or languages like Scala, where covariance can be switched off.
An example for covariance
In a simple example, the array reference is of type Object[]
but the array object and the elements are of different classes:
Object[] objectArray; // array reference objectArray = new String[3]; // array object; compatible assignment objectArray[0] = new Integer(5); // throws ArrayStoreException
En raison de la covariance, le compilateur ne peut pas vérifier l'exactitude de la dernière affectation aux éléments du tableau - la JVM le fait, et à des frais importants. Cependant, le compilateur peut optimiser la dépense, s'il n'y a pas d'utilisation de la compatibilité de type entre les types de tableaux.
Andreas SolymosiRappelez-vous qu'en Java, pour une variable de référence d'un certain type référant un objet de son supertype est interdit: les flèches de la figure 8 ne doivent pas être dirigées vers le haut.
Variances et caractères génériques dans les types génériques
Les types génériques (paramétrés) sont implicitement invariants en Java, ce qui signifie que différentes instanciations d'un type générique ne sont pas compatibles entre elles. Même la fonte de type n'entraînera pas de compatibilité:
Generic superGeneric; Generic subGeneric; subGeneric = (Generic)superGeneric; // type error superGeneric = (Generic)subGeneric; // type error
Les erreurs de type surviennent même si subGeneric.getClass() == superGeneric.getClass()
. Le problème est que la méthode getClass()
détermine le type brut - c'est pourquoi un paramètre de type n'appartient pas à la signature d'une méthode. Ainsi, les deux déclarations de méthode
void method(Generic p); void method(Generic p);
ne doit pas apparaître ensemble dans une définition d'interface (ou de classe abstraite).