Le polymorphisme
Notions théoriques
Qu'est-ce que le polymorphisme ?
Le mot polymorphisme vient du grec : "plusieurs formes". En POO, cela signifie qu'une variable de type Personnage peut référencer un objet Guerrier, un objet Mage, ou un objet Archer — et appeler la bonne méthode selon le type réel de l'objet.
Personnage p = new Guerrier("Thor", 120, 18, "épée runique");
p.attaquer(cible); // appelle la méthode attaquer() de Guerrier, pas de Personnage
C'est ce qu'on appelle le dispatch dynamique : Java choisit quelle implémentation de attaquer() appeler au moment de l'exécution, selon le type réel de l'objet (et non le type déclaré de la variable).
Liste hétérogène
Le polymorphisme devient particulièrement puissant avec les listes hétérogènes : une List<Personnage> peut contenir des Guerriers, des Mages, des Archers... et on peut itérer dessus en appelant attaquer() sur chacun.
import java.util.ArrayList;
import java.util.List;
List<Personnage> equipe = new ArrayList<>();
equipe.add(new Guerrier("Thor", 120, 18, "épée"));
equipe.add(new Mage("Elara", 80, 12, 60));
equipe.add(new Archer("Lyra", 90, 14, 20));
Personnage ennemi = new Guerrier("Golem", 200, 8, "poing");
// Chaque personnage attaque à sa manière, sans if/else
for (Personnage p : equipe) {
p.attaquer(ennemi); // dispatch dynamique : Guerrier, Mage ou Archer selon l'objet réel
}
instanceof classique
L'opérateur instanceof permet de tester si un objet est une instance d'un type particulier. Utile avant un cast.
Personnage p = new Guerrier("Thor", 120, 18, "épée");
if (p instanceof Guerrier) {
Guerrier g = (Guerrier) p; // cast explicite
g.coupEpee(ennemi); // méthode spécifique à Guerrier
}
Pattern matching avec instanceof (Java 16+)
Depuis Java 16, une syntaxe plus concise évite le cast manuel :
if (p instanceof Guerrier g) {
g.coupEpee(ennemi); // g est automatiquement typé Guerrier, pas besoin de cast
}
La variable g est disponible uniquement dans le bloc if.
Cast explicite et ClassCastException
Si vous tentez de caster un objet vers un type incompatible, Java lève une ClassCastException à l'exécution.
Personnage p = new Mage("Elara", 80, 12, 60);
Guerrier g = (Guerrier) p; // ERREUR à l'exécution : ClassCastException !
// car p est réellement un Mage, pas un Guerrier
Toujours vérifier avec instanceof avant de caster, sauf si vous êtes certain du type réel. Le pattern matching (Java 16) combine les deux en une seule opération sécurisée.
Exemple pratique
import java.util.ArrayList;
import java.util.List;
// Fichier : Main.java
public class Main {
public static void main(String[] args) {
// Liste hétérogène : tous sont des Personnage, mais de types différents
List<Personnage> equipeHeroes = new ArrayList<>();
equipeHeroes.add(new Guerrier("Thor", 120, 18, "épée runique"));
equipeHeroes.add(new Mage("Elara", 80, 12, 60));
equipeHeroes.add(new Guerrier("Brunhild", 130, 16, "hache de guerre"));
Personnage boss = new Mage("Sauron", 300, 20, 200);
System.out.println("=== Tour d'attaque ===");
for (Personnage p : equipeHeroes) {
if (p.estVivant()) {
p.attaquer(boss); // dispatch dynamique
}
}
System.out.println("\n=== Bilan après le tour ===");
System.out.println(boss.nom + " a " + boss.pointsDeVie + " PV restants.");
// Pattern matching Java 16 : accès aux méthodes spécifiques
System.out.println("\n=== Capacités spéciales ===");
for (Personnage p : equipeHeroes) {
if (p instanceof Mage m) {
System.out.println(m.nom + " a " + m.mana + " mana restant.");
} else if (p instanceof Guerrier g) {
System.out.println(g.nom + " utilise son " + g.typeArme + ".");
}
}
}
}
Sans polymorphisme, on devrait écrire un if (p instanceof Guerrier) ... else if (p instanceof Mage) ... pour chaque action. Avec le polymorphisme et la méthode abstraite attaquer(), une seule ligne suffit pour tous les types de personnages.
Test de mémorisation/compréhension
TP pour réfléchir et résoudre des problèmes
Vous allez créer une boucle de combat entre deux équipes en utilisant le polymorphisme pour appeler attaquer() sans savoir quel type de personnage on manipule.
Étape 1 — Créer deux équipes hétérogènes
Dans Main, créez deux listes de Personnage : une équipe de héros et une équipe d'ennemis.
Déclarez vos variables avec le type le plus général possible : List<Personnage> plutôt que ArrayList<Personnage>. Cela rend le code plus flexible : vous pourrez changer ArrayList en LinkedList sans modifier le reste du code.
Étape 2 — Boucle de combat par tour
Implémentez une boucle où chaque héros attaque le premier ennemi encore vivant.
Toujours vérifier que la cible est encore vivante avant d'attaquer. Sans cette vérification, un personnage mort continuerait de recevoir des attaques et ses PV deviendraient négatifs, ce qui peut provoquer des comportements inattendus.
Étape 3 — Utiliser le pattern matching pour les capacités spéciales
Après le combat, affichez les mana restants des Mages et les armes des Guerriers en utilisant le pattern matching Java 16.
Le pattern matching (instanceof Guerrier g) est plus sûr et plus lisible que le cast explicite ((Guerrier) p). Il évite une ClassCastException accidentelle et réduit le nombre de lignes de code.