Version sans JavaScript, en lecture seule : les ateliers, les quiz et l’exécution du code n’y sont pas. Version complète · Catalogue · Se connecter
Support de cours · chapitre 6
Le type déclaré et le type réel
Le compilateur se fonde sur le type déclaré, l'exécution sur l'objet réel. Ce chapitre distingue les deux, source d'erreurs fréquentes à l'audit de rentrée.
Ce chapitre est une révision de la COOA : sa notion (N1) est vérifiée par le diagnostic du 21/10, et n'est pas à l'épreuve.
Java est à la fois statiquement typé et dynamiquement lié. Ces deux propriétés ne se contredisent pas, car elles interviennent à deux moments différents.
Ce que vérifie le compilateur
CanardEnPlastique a = new CanardEnPlastique("Gérard", 499);
CanardSwag b = new CanardSwag("Jacqueline", 999, "cape");
a.cancaner(); // coin coin
b.cancaner(); // coin (mais stylé)
b.getAccessoire(); // légal : b est déclaré CanardSwag
En revanche :
CanardEnPlastique a = new CanardEnPlastique("Jean", 300);
a.getAccessoire(); // refusé : CanardEnPlastique n'a pas cette méthode
L'appel serait refusé même si a contenait un CanardSwag à l'exécution. Le compilateur ne tient pas compte de ce que la variable contiendra, seulement de son type déclaré.
Ce qui se passe à l'exécution
CanardEnPlastique c = new CanardSwag("Ouarzazate", 799, "chapeau");
c.cancaner(); // coin (mais stylé)
Le type déclaré est CanardEnPlastique, le type réel est CanardSwag. C'est la redéfinition du type réel qui s'exécute : c'est ce qu'on appelle le polymorphisme.
Exemple complet
Avant de lancer le programme, essayez de prévoir ce qu'affiche chacune des trois lignes.
Typage.java
class CanardEnPlastique {
public String crier() { return "coin"; }
}
class CanardSwag extends CanardEnPlastique {
@Override public String crier() { return "yo"; }
public String accessoire() { return "cape"; }
}
class Demo {
public static void main(String[] args) {
CanardEnPlastique c = new CanardSwag();
System.out.println("1. c.crier() : " + c.crier());
System.out.println("2. c.getClass() : " + c.getClass().getSimpleName());
// 3. Décommentez : le compilateur refuse, alors que l'objet SAIT le faire.
// System.out.println("3. c.accessoire() : " + c.accessoire());
// Le downcast rend la méthode visible, à vos risques.
CanardSwag swag = (CanardSwag) c;
System.out.println("4. après downcast : " + swag.accessoire());
// Et voici ce qui arrive quand l'objet n'est pas ce qu'on prétend.
CanardEnPlastique simple = new CanardEnPlastique();
try {
CanardSwag rate = (CanardSwag) simple;
System.out.println(rate.accessoire());
} catch (ClassCastException e) {
System.out.println("5. downcast raté : " + e.getClass().getSimpleName());
}
}
}
Ici, un atelier : on y écrit et on y exécute du code. Il demande JavaScript.
La ligne 2 montre que l'objet est bien un CanardSwag. La ligne 3, si vous la décommentez, montre que le compilateur n'en tient pas compte et refuse l'appel.
Les casts
Vers le haut (upcast). Il est implicite et toujours sûr :
CanardSwag swag = new CanardSwag("Jacqueline", 999, "cape");
CanardEnPlastique canard = swag; // pas de parenthèses nécessaires
En effet, un CanardSwag est forcément un CanardEnPlastique.
Vers le bas (downcast). Il doit être écrit explicitement, et le compilateur l'accepte sans vérifier :
CanardEnPlastique canard = new CanardEnPlastique("Jean", 399);
CanardSwag swag = (CanardSwag) canard; // compile
swag.getAccessoire(); // ClassCastException à l'exécution
Les limites d'instanceof
if (c instanceof CanardSwag) {
CanardSwag s = (CanardSwag) c;
System.out.println("Accessoire : " + s.getAccessoire());
}
Ce code fonctionne, mais il se maintient mal :
- il est fragile : à chaque nouvelle sous-classe, il faut retrouver tous les
instanceof; - il est fermé à l'extension : chaque nouveau type oblige à modifier du code existant ;
- il est verbeux : trois lignes là où une redéfinition n'en demande aucune à l'appel.
Dans la version polymorphe, on appelle directement une méthode de l'objet au lieu de tester son type :
class CanardEnPlastique {
public String description() { return "Canard basique"; }
}
class CanardSwag extends CanardEnPlastique {
@Override public String description() { return "Swag avec " + accessoire; }
}
void afficher(CanardEnPlastique c) {
System.out.println(c.description()); // plus aucun test de type
}
Question d'épreuve
CanardEnPlastique c = new CanardSwag();, quelle méthodecrier()s'exécute, et pourquoi le compilateur refuse-t-ilc.accessoire()?
C'est celle de CanardSwag, car la liaison est dynamique : c'est le type réel de l'objet qui compte. En revanche, c.accessoire() est refusé parce que le compilateur ne considère que le type déclaré, CanardEnPlastique, qui n'a pas cette méthode, quel que soit l'objet contenu dans la variable à l'exécution.