13 - Héritage
Apprenez à créer des hiérarchies de classes et réutiliser du code efficacement.
L'héritage vous permet de créer de nouvelles classes basées sur des classes existantes La nouvelle classe hérite des toutes les propriétés et méthodes de la classe parent, et peut ajouter les siennes ou modifier celles héritées.
Qu'est-ce que l'héritage
Pensez à l'héritage comme un arbre généalogique Un enfant hérite des traits de ses parents, mais peut aussi avoir ses propres traits uniques.
// Classe parent (superclasse)
public class Entity {
protected String name;
protected int health;
public Entity(String name, int health) {
this.name = name;
this.health = health;
}
public void takeDamage(int damage) {
health -= damage;
System.out.println(name + " à pris " + damage + " dégâts !");
}
}
// Classe enfant (sous-classe)
public class Player extends Entity {
private int level;
public Player(String name, int health, int level) {
super(name, health); // Appelle le constructeur du parent
this.level = level;
}
public void levelUp() {
level++;
System.out.println(name + " est passé au niveau " + level + " !");
}
}- Superclasse/Parent : la classe dont on hérite (Entity)
- Sous-classe/Enfant : la classe qui hérite (Player)
- extends : mot clé pour hériter d'une classe
- super : mot clé pour accéder aux membres de la classe parent
public class Monster extends Entity {
// Monster EST UNE Entity
// Monster hérite d'Entity
// Entity est le parent, Monster est l'enfant
}Le mot clé extends
Utilisez extends pour hériter d'une classe :
public class Animal {
protected String name;
public void makeSound() {
System.out.println(name + " fait un son");
}
}
public class Dog extends Animal {
public void wagTail() {
System.out.println(name + " remue la queue");
}
}
public class Main {
public static void main(String[] args) {
Dog dog = new Dog();
dog.name = "Buddy";
dog.makeSound(); // Hérité d'Animal
dog.wagTail(); // La méthode propre à Dog
}
}Le mot clé super
super fait référence à la classe parent :
Appeler le constructeur du parent
public class Entity {
protected String name;
public Entity(String name) {
this.name = name;
}
}
public class Player extends Entity {
private int level;
public Player(String name, int level) {
super(name); // Appelle le constructeur du parent en PREMIER
this.level = level;
}
}super()doit être la première instruction dans le constructeur de l'enfant- Si vous n'appelez pas
super(), Java appelle automatiquement le constructeur parent sans arguments - Si le parent n'a pas de constructeur sans arguments, vous DEVEZ appeler
super()avec les arguments
// Incorrect - super() n'est pas en premier
public Player(String name, int level) {
this.level = level;
super(name); // Erreur !
}
// Correct
public Player(String name, int level) {
super(name); // Première instruction
this.level = level;
}Appeler les méthodes du parent
public class Entity {
protected int health;
public void takeDamage(int damage) {
health -= damage;
System.out.println("L'entité à pris des dégâts!");
}
}
public class Player extends Entity {
@Override
public void takeDamage(int damage) {
super.takeDamage(damage); // Appelle la version du parent
if (health < 20) {
System.out.println("Attention : vie faible !");
}
}
}Surcharge de méthode
Les classes enfant peuvent remplacer les méthodes du parent :
public class Entity {
public void attack() {
System.out.println("L'entité attaque !");
}
}
public class Player extends Entity {
@Override // Bonne pratique d'utiliser cette annotation
public void attack() {
System.out.println("Le joueur balance son épée !");
}
}
public class Monster extends Entity {
@Override
public void attack() {
System.out.println("Le monstre mord !");
}
}
public class Main {
public static void main(String[] args) {
Player player = new Player();
Monster monster = new Monster();
player.attack(); // "Le joueur balance son épée !"
monster.attack(); // "Le monstre mord !"
}
}Utilisez toujours @Override lorsque vous surchargez une méthode :
- Vous aide à remarquer les erreurs de typo (si la méthode n'existe pas dans le parent, vous obtenez une erreur)
- Rend le code plus clair
- Bonne documentation
// Sans @Override - l'erreur de typo n'est pas forcément remarquée
public void attac() { // Erreur de typo ! Crée une nouvelle méthode au lieu de remplacer
// ...
}
// Avec @Override - erreur remarquée directement
@Override
public void attac() { // Erreur : la méthode n'existe pas dans le parent
// ...
}Modificateurs d'accès avec l'héritage
public- Accessible partoutprotected- Accessible dans la classe et ses sous-classesprivate- Accessible seulement dans la classe (pas hérité)
public class Parent {
public int publicVar; // L'enfant peut accéder
protected int protectedVar; // L'enfant peut accéder
private int privateVar; // L'enfant ne peut pas accéder
private void privateMethod() {
// L'enfant ne peut pas appeler
}
protected void protectedMethod() {
// L'enfant peut appeler
}
}
public class Child extends Parent {
public void test() {
publicVar = 10; // OK
protectedVar = 20; // OK
privateVar = 30; // Erreur !
protectedMethod(); // OK
privateMethod(); // Erreur !
}
}Exemples pratiques
Hiérarchie des entités du jeu
// Classe de base pour toutes les entités
public class Entity {
protected String name;
protected int health;
protected int maxHealth;
protected double x, y, z;
public Entity(String name, int maxHealth) {
this.name = name;
this.health = maxHealth;
this.maxHealth = maxHealth;
}
public void takeDamage(int damage) {
health -= damage;
if (health < 0) health = 0;
System.out.println(name + " à pris " + damage + " dégâts. Vie : " + health);
}
public boolean isAlive() {
return health > 0;
}
public void moveTo(double x, double y, double z) {
this.x = x;
this.y = y;
this.z = z;
System.out.println(name + " déplacé en (" + x + ", " + y + ", " + z + ")");
}
}
// Joueur hérite d'Entité
public class Player extends Entity {
private int level;
private int experience;
private int mana;
public Player(String name) {
super(name, 100);
this.level = 1;
this.experience = 0;
this.mana = 50;
}
public void gainExperience(int amount) {
experience += amount;
System.out.println("A gagné " + amount + " XP");
if (experience >= level * 100) {
levelUp();
}
}
private void levelUp() {
level++;
maxHealth += 10;
health = maxHealth;
mana += 5;
System.out.println("Niveau supérieur ! Maintenant niveau " + level);
}
@Override
public void takeDamage(int damage) {
super.takeDamage(damage);
if (health < maxHealth * 0.25) {
System.out.println("⚠ ATTENTION : Vie faible !");
}
}
}
// Monstre hérite d'Entité
public class Monster extends Entity {
private int attackPower;
private String type;
public Monster(String name, int health, int attackPower, String type) {
super(name, health);
this.attackPower = attackPower;
this.type = type;
}
public int attack() {
System.out.println(name + " attaque !");
return attackPower;
}
@Override
public void takeDamage(int damage) {
super.takeDamage(damage);
if (!isAlive()) {
System.out.println(name + " a été vaincu !");
}
}
}
// Boss hérite de Monstre (héritage à plusieurs niveaux)
public class Boss extends Monster {
private int phase;
public Boss(String name, int health, int attackPower) {
super(name, health, attackPower, "Boss");
this.phase = 1;
}
@Override
public void takeDamage(int damage) {
super.takeDamage(damage);
// Change de phase à 50% de vie
if (phase == 1 && health < maxHealth / 2) {
phase = 2;
System.out.println(name + " entre en PHASE 2 !");
}
}
@Override
public int attack() {
int damage = super.attack();
if (phase == 2) {
damage *= 2;
System.out.println("ATTAQUE ENRAGÉE !");
}
return damage;
}
}Hiérarchie d'objet
// Classe de base d'Objet
public class Item {
protected String name;
protected int value;
protected double weight;
public Item(String name, int value, double weight) {
this.name = name;
this.value = value;
this.weight = weight;
}
public void use() {
System.out.println("Utilisation de " + name);
}
public String getInfo() {
return name + " ($" + value + ", " + weight + " kg)";
}
}
// Arme hérite d'Objet
public class Weapon extends Item {
private int damage;
private int durability;
public Weapon(String name, int value, double weight, int damage, int durability) {
super(name, value, weight);
this.damage = damage;
this.durability = durability;
}
@Override
public void use() {
if (durability > 0) {
System.out.println("Attaque avec " + name + " pour " + damage + " dégâts !");
durability--;
} else {
System.out.println(name + " est cassé !");
}
}
@Override
public String getInfo() {
return super.getInfo() + ", Dégâts : " + damage + ", Durabilité : " + durability;
}
}
// Consommable hérite d'Objet
public class Consumable extends Item {
private int healAmount;
private int uses;
public Consumable(String name, int value, double weight, int healAmount, int uses) {
super(name, value, weight);
this.healAmount = healAmount;
this.uses = uses;
}
@Override
public void use() {
if (uses > 0) {
System.out.println("Utilisation de " + name + ", à restauré " + healAmount + " points de vie !");
uses--;
} else {
System.out.println("Aucun(e) " + name + " restante !");
}
}
@Override
public String getInfo() {
return super.getInfo() + ", Soigne : " + healAmount + ", Utilisations : " + uses;
}
}
// Armure hérite d'Objet
public class Armor extends Item {
private int defense;
private String slot;
public Armor(String name, int value, double weight, int defense, String slot) {
super(name, value, weight);
this.defense = defense;
this.slot = slot;
}
@Override
public void use() {
System.out.println("Vous avez équippé " + name + " (+" + defense + " défense)");
}
@Override
public String getInfo() {
return super.getInfo() + ", Défense : " + defense + ", Emplacement : " + slot;
}
}Polymorphisme
Les objets enfants peuvent être considérés comme des objets parents :
Entity entity1 = new Player("Alice");
Entity entity2 = new Monster("Goblin", 50, 10, "Hostile");
Entity entity3 = new Boss("Dragon", 500, 50);
// Tous peuvent utiliser les méthodes d'Entity
entity1.takeDamage(10);
entity2.takeDamage(10);
entity3.takeDamage(10);
// Tableau de différent types
Entity[] entities = {
new Player("Bob"),
new Monster("Zombie", 30, 8, "Hostile"),
new Monster("Spider", 20, 5, "Hostile")
};
// Traite toutes les entités de la même manière
for (Entity entity : entities) {
entity.takeDamage(5);
}Le polymorphisme vous permet d'écrire du code qui marche avec le type parent, mais gère le type enfant correctement :
public void damageEntity(Entity entity, int damage) {
entity.takeDamage(damage);
// Fonctionne pour le joueur, monstre, boss, etc.
// Chacun utilise sa propre version de takeDamage()
}
// Peut appler avec n'importe quel type d'entité
damageEntity(new Player("Alice"), 10);
damageEntity(new Monster("Goblin", 50, 10, "Hostile"), 10);
damageEntity(new Boss("Dragon", 500, 50), 10);La classe Object
Toutes les classes en Java héritent automatiquement de la classe Object :
public class MyClass {
// Hérite automatiquement d'Object
// A des méthodes comme toString(), equals(), etc.
}Méthodes d'Object courantes à surcharger :
public class Player {
private String name;
private int level;
@Override
public String toString() {
return "Joueur : " + name + " (Niv. " + level + ")";
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Player) {
Player other = (Player) obj;
return this.name.equals(other.name);
}
return false;
}
}Classes et méthodes finales
final empêche l'héritage ou la surcharge :
// Class finale - ne peut pas être héritée
public final class SpecialItem {
// Aucune classe ne peut hériter de ça
}
// Méthode finale - ne peut pas être surchargée
public class Entity {
public final void printName() {
System.out.println(name);
}
}
public class Player extends Entity {
@Override
public void printName() { // Erreur ! La méthode est finale
// Ne peut pas surcharger
}
}Erreurs courantes
// Incorrect - Oubli de super()
public class Player extends Entity {
public Player(String name) {
// Erreur ! Entity n'a pas de constructeur sans arguments
}
}
// Correct
public class Player extends Entity {
public Player(String name) {
super(name, 100); // Appelle le constructeur du parent
}
}
// Incorrect - Accès à un membre privé
public class Child extends Parent {
public void test() {
privateVar = 10; // Erreur ! les membres privés ne s'héritent pas
}
}
// Correct - Utilisez protected
public class Parent {
protected int protectedVar; // L'enfant peut y accéder
}
// Incorrect - Héritage multiple (pas autorisé en Java)
public class Child extends Parent1, Parent2 { // Erreur !
}
// Correct - Héritage unique seulement
public class Child extends Parent {
}Exercices pratiques
-
Hiérarchie de véhicules : créez une classe
Vehicle(Véhicule) avec des propriétés commespeed(vitesse) etfuel(carburant). Créez les sous-classesCar(Voiture) etMotorcycle(Moto) avec leurs propres fonctionnalités. -
Calculateur de forme : créez une classe
Shape(Forme) avec une méthodecalculateArea()pour calculer l'aire. Créez les sous-classesCircle(Cercle),RectangleetTrianglequi surchargent cette méthode. -
Personnages de RPG : créez une classe
Character(Personnage). Réalisez les sous-classesWarrior(Guerrier),MageetArcheravec leurs capacités uniques. -
Sons d'animaux : créez une classe
Animalavec une méthodemakeSound()qui affiche le son qu'il produit. Créez plusieurs sous-classes d'animaux qui surchargent cette méthode.