Nous avons vu comment il était possible de construire une hiéarchie
de classes par extension (construction de sous-classes) de classes existantes.
La classe Object constitue la racine
de la hiérarchie des classes : toute nouvelle classe en constituera une
sous-classe (par transitivité). Le transtypage est, pour les références et les objets,
la possibilité d'avoir d'un objet une vue spécifique.
Alors que, quand il s'applique à un type primitif, le transtypage donne lieu à une transformation de la représentation de la valeur à laquelle il est appliqué, lorsqu'il s'applique à une référence, il ne change en rien l'objet sous-jacent. L'opération est purement syntaxique et indique au compilateur le point de vue depuis lequel il doit voir l'objet, ce qui agit d'une part au niveau de l'analyse qu'il réalise statiquement des types des objets et d'autre part sur la vision qu'il en a en ce qui concerne les variables d'instance.
Nous avons dit qu'il était possible d'utiliser une valeur de type ST en lieu et place d'une valeur de type T si ST est un sous-type de T : ce type de transtypage vers un sur-type est appelé «transtypage vers le haut» («upcasting»).
Ainsi, si par exemple, si ST est un sous-type de T, la séquence
ST refST = new ST(...); T refT = refST; Object refObj = refST; |
est légale et après exécution, les trois variables
refST,
refT et refObj
référenceront toutes les trois le même objet de type
ST
créé lors de l'instanciation de la classe
ST avec l'opérateur
new.
Il s'agit là d'une forme de transtypage (cast) implicite. Si on peut par exemple rapprocher ce type de transtypage de celui réalisé lors de l'affectation d'un caractère à un entier, insistons sur le fait qu'il ne réalise aucune transformation de l'objet sous-jacent.
Le transtypage dans << l'autre sens >> (dit vers le bas), c'est-à-dire l'interprétation d'une expression d'un type T dans un sous-type ST de T est nécessairement explicite. Ainsi, l'affectation << brutale >> à une variable de type ST d'une expression de type T, sur-type de type ST est rejetée par le compilateur:
--> cat Interdit1.java
class X {
int n;
}
class Y extends X { } // Y sous-classe de X
public class Interdit1 {
public static void main(String[ ] argv){
X x = new X( );
Y y = x;
}
}
--> javac Interdit1.java
Interdit1.java:8 incompatible types
found : X
required: Y
Y y = x;
^
1 error
|
--> cat Interdit2.java
class X {
int n;
}
class Y extends X{ }
public class Interdit2 {
public static void main(String[ ] argv){
X x = new X( );
Y y = ( Y ) x; // un transtypage explicite
}
}
--> javac Interdit2.java // le compilateur ne bronche pas
-->
|
Il est bien clair que, si cela garantit une acceptation par le compilateur,
rien ne garantit qu'à l'exécution les choses se passeront bien.
Sur notre exemple, ce n'est pas le cas et l'exécution donne lieu à une levée d'exception :
--> java Interdit2
Exception in thread "main" java.lang.ClassCastException : X
at Interdit2.main(Interdit2.java:8)
|
|
Soient
Le transtypage est possible, tant à la compilation qu'à l'exécution
|