Le transtypage («cast»)

Introduction

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.


Le transtypage vers le haut («upcasting»)

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 vers le bas («downcasting»)

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

Il est nécessaire, dans une situation comme celle-ci, de réaliser un transtypage explicite :


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

Règle de correction d'un transtypage vers le bas («downcasting«)



     Soient
  • T1 le type du transtypage,
  • T2 le type de la référence transtypée
  • T3 le type réel de l'objet référencé
     comme par exemple dans T1 ref = (T2) new T3( ) ;

     Le transtypage est possible, tant à la compilation qu'à l'exécution     
                              si et seulement si
     T3 est un sous-type de T2, lui-même étant un sous-type de T1
                         autrement dit si et seulement si
     la référence de type T2 fait référence à un objet ayant comme
     type réel un type T3 sous-type de T1.