Hytale Modding
Основи Java

13 — Успадкування (Inheritance)

Дізнайтеся, як створювати ієрархії класів та ефективно використовувати код повторно.

Спадкування дозволяє створювати нові класи на основі наявних. Новий клас успадковує всі властивості та методи батьківського класу, а також може додавати власні або змінювати успадковані.

Що таке успадкування?

Уявіть успадкування як сімейне дерево. Дитина успадковує риси від своїх батьків, але також може мати свої унікальні особливості.

// Батьківський клас (суперклас)
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 + " отримав " + damage + " одиниць шкоди!");
    }
}

// Дочірній клас (підклас)
public class Player extends Entity {
    private int level;
    
    public Player(String name, int health, int level) {
        super(name, health);  // Виклик конструктора батька
        this.level = level;
    }
    
    public void levelUp() {
        level++;
        System.out.println(name + " підняв рівень до " + level + "!");
    }
}
Термінологія спадкування
  • Суперклас/Батько: Клас, від якого успадковують (напр. Entity).
  • Підклас/Дитина: Клас, який успадковує (напр. Player).
  • extends: Ключове слово для створення зв'язку спадкування.
  • super: Ключове слово для доступу до членів батьківського класу.
public class Monster extends Entity {
    // Монстр — ЦЕ сутність
    // Монстр успадковує від Сутності
    // Сутність — це батько, Монстр — дитина
}

Ключове слово extends

Використовуйте extends для успадкування від класу:

public class Animal {
    protected String name;
    
    public void makeSound() {
        System.out.println(name + " видає звук");
    }
}

public class Dog extends Animal {
    public void wagTail() {
        System.out.println(name + " махає хвостом");
    }
}

public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.name = "Бадді";
        dog.makeSound();  // Успадковано від Animal
        dog.wagTail();    // Власний метод Dog
    }
}

Ключове слово super

super посилається на батьківський клас:

Виклик конструктора батька

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);  // Викликати конструктор батька ПЕРШИМ
        this.level = level;
    }
}
Правила конструкторів
  • super() має бути першою інструкцією в конструкторі дитини.
  • Якщо ви не викликаєте super(), Java автоматично викликає порожній конструктор батька.
  • Якщо у батька немає конструктора без аргументів, ви ЗОБОВ'ЯЗАНІ викликати super() з аргументами.
// НЕПРАВИЛЬНО — super() не на першому місці
public Player(String name, int level) {
    this.level = level;
    super(name);  // Помилка!
}

// ПРАВИЛЬНО
public Player(String name, int level) {
    super(name);  // Перша інструкція
    this.level = level;
}

Виклик методів батьківського класу

public class Entity {
    protected int health;
    
    public void takeDamage(int damage) {
        health -= damage;
        System.out.println("Сутність отримала шкоду!");
    }
}

public class Player extends Entity {
    @Override
    public void takeDamage(int damage) {
        super.takeDamage(damage);  // Викликаємо версію батька
        if (health < 20) {
            System.out.println("Попередження: Низьке здоров'я!");
        }
    }
}

Перевизначення методів (Method Overriding)

Дочірні класи можуть замінювати поведінку батьківських методів:

public class Entity {
    public void attack() {
        System.out.println("Сутність атакує!");
    }
}

public class Player extends Entity {
    @Override // Гарна практика використовувати цю анотацію
    public void attack() {
        System.out.println("Гравець замахується мечем!");
    }
}

public class Monster extends Entity {
    @Override
    public void attack() {
        System.out.println("Монстр кусається!");
    }
}

public class Main {
    public static void main(String[] args) {
        Player player = new Player();
        Monster monster = new Monster();
        
        player.attack();   // "Гравець замахується мечем!"
        monster.attack();  // "Монстр кусається!"
    }
}
Анотація @Override

Завжди використовуйте @Override при перевизначенні методів:

  • Допомагає виявити друкарські помилки: якщо методу не існує в батьківському класі, ви отримаєте помилку.
  • Робить код зрозумілішим.
  • Служить гарною документацією.
// БЕЗ @Override — помилку не помічено
public void attac() {  // Друкарська помилка! Просто створює новий метод
    // ...
}

// З @Override — помилку виявлено миттєво
@Override
public void attac() {  // Помилка: методу не існує в батьківському класі
    // ...
}

Модифікатори доступу при спадкуванні

  • public — доступний всюди.
  • protected — доступний у поточному класі та його підкласах.
  • private — тільки в межах класу (не успадковується).
public class Parent {
    public int publicVar;      // Дитина має доступ
    protected int protectedVar; // Дитина має доступ
    private int privateVar;     // Дитина НЕ МАЄ доступу
    
    private void privateMethod() { /* ... */ }    // Дитина не може викликати
    protected void protectedMethod() { /* ... */ } // Дитина може викликати
}

public class Child extends Parent {
    public void test() {
        publicVar = 10;      // ОК
        protectedVar = 20;   // ОК
        privateVar = 30;     // Помилка!
        
        protectedMethod();   // ОК
        privateMethod();     // Помилка!
    }
}

Практичні приклади

Ієрархія ігрових сутностей (Game Entity Hierarchy)

// Базовий клас для всіх сутностей
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 + " отримав " + damage + " одиниць шкоди. Здоров'я: " + 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 + " перемістився в координати (" + x + ", " + y + ", " + z + ")");
    }
}

// Player (Гравець) успадковує Entity
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("Отримано " + amount + " XP");
        
        if (experience >= level * 100) {
            levelUp();
        }
    }
    
    private void levelUp() {
        level++;
        maxHealth += 10;
        health = maxHealth;
        mana += 5;
        System.out.println("Новий рівень! Тепер рівень " + level);
    }
    
    @Override
    public void takeDamage(int damage) {
        super.takeDamage(damage);
        if (health < maxHealth * 0.25) {
            System.out.println("⚠ УВАГА: Низький рівень здоров'я!");
        }
    }
}

// Monster (Монстр) успадковує Entity
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 + " атакує!");
        return attackPower;
    }
    
    @Override
    public void takeDamage(int damage) {
        super.takeDamage(damage);
        if (!isAlive()) {
            System.out.println(name + " було переможено!");
        }
    }
}

// Boss (Бос) успадковує Monster (багаторівневе спадкування)
public class Boss extends Monster {
    private int phase;
    
    public Boss(String name, int health, int attackPower) {
        super(name, health, attackPower, "Бос");
        this.phase = 1;
    }
    
    @Override
    public void takeDamage(int damage) {
        super.takeDamage(damage);
        
        // Зміна фази при 50% здоров'я
        if (phase == 1 && health < maxHealth / 2) {
            phase = 2;
            System.out.println(name + " переходить у ФАЗУ 2!");
        }
    }
    
    @Override
    public int attack() {
        int damage = super.attack();
        if (phase == 2) {
            damage *= 2;
            System.out.println("ЛЮТА АТАКА!");
        }
        return damage;
    }
}

Ієрархія предметів

// Базовий клас предмета
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("Використання: " + name);    
    }        
    
    public String getInfo() {        
        return name + " ($" + value + ", " + weight + " кг)";    
    }
}

// Weapon (Зброя) успадковує Item
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("Атака предметом " + name + ". Шкода: " + damage + "!");            
            durability--;        
        } else {            
            System.out.println(name + " зламано!");        
        }    
    }        
    
    @Override    
    public String getInfo() {        
        return super.getInfo() + ", Шкода: " + damage + ", Міцність: " + durability;    
    }
}

// Consumable (Витратні предмети) успадковує Item
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("Використано " + name + ", відновлено " + healAmount + " здоров'я!");            
            uses--;        
        } else {            
            System.out.println(name + " закінчився!");        
        }    
    }        
    
    @Override    
    public String getInfo() {        
        return super.getInfo() + ", Лікування: " + healAmount + ", Залишилось використань: " + uses;    
    }
}

// Armor (Броня) успадковує Item
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("Екіпіровано " + name + " (+" + defense + " захисту)");    
    }        
    
    @Override    
    public String getInfo() {        
        return super.getInfo() + ", Захист: " + defense + ", Слот: " + slot;    
    }
}

Поліморфізм

Дочірні об'єкти можуть розглядатися як об'єкти батьківського класу:

Entity entity1 = new Player("Alice");
Entity entity2 = new Monster("Goblin", 50, 10, "Hostile");
Entity entity3 = new Boss("Dragon", 500, 50);

// Усі можуть використовувати методи Entity
entity1.takeDamage(10);
entity2.takeDamage(10);
entity3.takeDamage(10);

// Масив різних типів
Entity[] entities = {
    new Player("Bob"),
    new Monster("Zombie", 30, 8, "Hostile"),
    new Monster("Spider", 20, 5, "Hostile")
};

// Обробка всіх сутностей однаковим способом
for (Entity entity : entities) {
    entity.takeDamage(5);
}
Переваги поліморфізму

Поліморфізм дозволяє писати код, який працює з батьківськими типами, але коректно обробляє дочірні типи:

public void damageEntity(Entity entity, int damage) {
    entity.takeDamage(damage);
    // Працює для Player, Monster, Boss тощо.
    // Кожен використовує власну версію takeDamage()
}

// Можна викликати з будь-яким типом Entity
damageEntity(new Player("Alice"), 10);
damageEntity(new Monster("Goblin", 50, 10, "Hostile"), 10);
damageEntity(new Boss("Dragon", 500, 50), 10);

Клас Object

Усі класи в Java автоматично успадковуються від Object:

public class MyClass {
    // Автоматично розширює (extends) Object
    // Має такі методи, як toString(), equals() тощо.
}

Поширені методи Object для перевизначення:

public class Player {
    private String name;
    private int level;
    
    @Override
    public String toString() {
        return "Гравець: " + name + " (Рівень " + level + ")";
    }
    
    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Player) {
            Player other = (Player) obj;
            return this.name.equals(other.name);
        }
        return false;
    }
}

Фінальні класи та методи

Ключове слово final запобігає успадкуванню або перевизначенню:

// Фінальний клас — не може бути успадкований
public final class SpecialItem {
    // Жоден клас не може розширити цей
}

// Фінальний метод — не може бути перевизначений
public class Entity {
    public final void printName() {
        System.out.println(name);
    }
}

public class Player extends Entity {
    @Override
    public void printName() {  // Помилка! Метод є final
        // Неможливо перевизначити
    }
}

Поширені помилки

// НЕПРАВИЛЬНО — забуто super()
public class Player extends Entity {
    public Player(String name) {
        // Помилка! Entity не має конструктора без аргументів
    }
}

// ПРАВИЛЬНО
public class Player extends Entity {
    public Player(String name) {
        super(name, 100);  // Виклик конструктора батька
    }
}

// НЕПРАВИЛЬНО — доступ до private членів
public class Child extends Parent {
    public void test() {
        privateVar = 10;  // Помилка! private не успадковується
    }
}

// ПРАВИЛЬНО — використовуйте protected
public class Parent {
    protected int protectedVar;  // Дитина має доступ
}

// НЕПРАВИЛЬНО — множинне спадкування (заборонено в Java)
public class Child extends Parent1, Parent2 {  // Помилка!
}

// ПРАВИЛЬНО — тільки одиничне спадкування
public class Child extends Parent {
}

Практичні вправи

  1. Ієрархія транспорту: Створіть клас Vehicle (Транспорт) з властивостями швидкості та пального. Створіть підкласи Car та Motorcycle з їхніми унікальними особливостями.

  2. Калькулятор фігур: Створіть клас Shape з методом calculateArea(). Створіть підкласи Circle, Rectangle та Triangle, які перевизначають цей метод.

  3. RPG Персонажі: Створіть клас Character. Розширте його, щоб створити класи Warrior, Mage та Archer з унікальними здібностями.

  4. Звуки тварин: Створіть клас Animal з методом makeSound(). Створіть різні підкласи тварин, які перевизначають цей метод.