Modding d'Hytale
Bases de Java

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 + " !");
    }
}
Terminologie de l'héritage
  • 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();  //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;
    }
}
Règles du constructeur
  • 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 !"
    }
}
L'annotation @Override

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 partout
  • protected - Accessible dans la classe et ses sous-classes
  • private - 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);
}
Les bénéfices du polymorphisme

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

  1. Hiérarchie de véhicules : créez une classe Vehicle (Véhicule) avec des propriétés comme speed (vitesse) et fuel (carburant). Créez les sous-classes Car (Voiture) et Motorcycle (Moto) avec leurs propres fonctionnalités.

  2. Calculateur de forme : créez une classe Shape (Forme) avec une méthode calculateArea() pour calculer l'aire. Créez les sous-classes Circle (Cercle), Rectangle et Triangle qui surchargent cette méthode.

  3. Personnages de RPG : créez une classe Character (Personnage). Réalisez les sous-classes Warrior (Guerrier), Mage et Archer avec leurs capacités uniques.

  4. Sons d'animaux : créez une classe Animal avec une méthode makeSound() qui affiche le son qu'il produit. Créez plusieurs sous-classes d'animaux qui surchargent cette méthode.