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 — помилку не помічено
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 {
}Практичні вправи
-
Ієрархія транспорту: Створіть клас
Vehicle(Транспорт) з властивостями швидкості та пального. Створіть підкласиCarтаMotorcycleз їхніми унікальними особливостями. -
Калькулятор фігур: Створіть клас
Shapeз методом calculateArea(). Створіть підкласиCircle,RectangleтаTriangle, які перевизначають цей метод. -
RPG Персонажі: Створіть клас
Character. Розширте його, щоб створити класиWarrior,MageтаArcherз унікальними здібностями. -
Звуки тварин: Створіть клас
Animalз методом makeSound(). Створіть різні підкласи тварин, які перевизначають цей метод.