1. Абстрактні класи та методи
Іноді у житті (й у програмуванні) хочеться сказати: «Ну, я не знаю, як саме це робиться, але точно знаю, що це має бути!» Наприклад, усі тварини мають уміти видавати звук, але який саме — залежить від конкретної тварини. Саме для таких випадків у Java створили абстрактні класи та абстрактні методи.
Абстрактний клас — це клас, який не можна створити напряму (не можна написати new Animal(), якщо Animal абстрактний), але від якого можна успадковувати. Такий клас може містити як звичайні (реалізовані) методи, так і абстрактні — тобто оголошені, але не реалізовані.
Абстрактний метод — це метод без тіла. Він оголошується за допомогою ключового слова abstract і обовʼязково має бути реалізований у підкласах (якщо тільки підклас сам не є абстрактним).
Приклад з життя
Припустімо, що маємо застосунок для зоопарку. Хочемо, аби всі тварини мали метод makeSound(), але не знаємо, який саме звук вони видають. Тоді створюємо абстрактний клас:
public abstract class Animal {
public abstract void makeSound(); // Абстрактний метод
}
Конкретні тварини реалізують цей метод по‑своєму:
public class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("Гав-гав!");
}
}
public class Cat extends Animal {
@Override
public void makeSound() {
System.out.println("Няв!");
}
}
Тепер, якщо хтось спробує створити new Animal(), компілятор відразу повідомить: «Вибачте, але абстрактних тварин у природі не буває!» Це корисно: ви гарантуєте, що у програмі існуватимуть лише конкретні тварини з конкретною поведінкою.
2. Поліморфізм через абстракцію
Абстракція — це виокремлення спільного інтерфейсу для групи обʼєктів. Абстрактний клас якраз і визначає цей спільний інтерфейс: він вказує, які методи обовʼязково мають реалізувати всі нащадки.
Поліморфізм і абстракція працюють у парі: абстрактний клас гарантує, що в усіх нащадків є потрібні методи, а поліморфізм дозволяє викликати ці методи через посилання на базовий тип.
Приклад: створюємо зоопарк
Зберімо невеличкий зоопарк. Маємо абстрактний клас Animal і кілька його нащадків:
public abstract class Animal {
public abstract void makeSound();
}
public class Cow extends Animal {
@Override
public void makeSound() {
System.out.println("Му-у-у!");
}
}
public class Duck extends Animal {
@Override
public void makeSound() {
System.out.println("Кря-кря!");
}
}
Тепер ми можемо створити масив тварин:
Animal[] zoo = {
new Dog(),
new Cat(),
new Cow(),
new Duck()
};
for (Animal animal : zoo) {
animal.makeSound(); // Для кожної тварини буде викликано "правильний" метод
}
Кожен обʼєкт у масиві — це конкретна тварина, але для коду це просто Animal. Завдяки поліморфізму й абстракції ми певні, що кожен обʼєкт має метод makeSound(), і він спрацює коректно.
3. Використання абстрактних класів для поліморфізму
Розгляньмо практичніший приклад. Уявімо, що ми розробляємо застосунок для керування співробітниками компанії. У нас є різні типи співробітників: менеджери, розробники, тестувальники. Усі мають спільний метод work(), але виконують його по‑різному.
Абстрактний клас Employee
public abstract class Employee {
protected String name;
public Employee(String name) {
this.name = name;
}
public abstract void work();
}
Конкретні підкласи
public class Manager extends Employee {
public Manager(String name) {
super(name);
}
@Override
public void work() {
System.out.println(name + " керує командою.");
}
}
public class Developer extends Employee {
public Developer(String name) {
super(name);
}
@Override
public void work() {
System.out.println(name + " пише код.");
}
}
public class Tester extends Employee {
public Tester(String name) {
super(name);
}
@Override
public void work() {
System.out.println(name + " тестує застосунок.");
}
}
Використання поліморфізму
Тепер ми можемо створити масив співробітників і викликати для кожного метод work():
Employee[] employees = {
new Manager("Анна"),
new Developer("Іван"),
new Tester("Марія")
};
for (Employee e : employees) {
e.work();
}
Результат:
Анна керує командою.
Іван пише код.
Марія тестує застосунок.
Зверніть увагу: ми не знаємо (і не хочемо знати!) у циклі, який саме у нас тип співробітника. Ми просто викликаємо work(), і кожен обʼєкт робить свою справу.
4. Корисні нюанси
Гарантія реалізації методів
Абстрактний клас зобовʼязує всіх нащадків реалізувати потрібні методи. Якщо ви забудете реалізувати абстрактний метод у підкласі, компілятор відразу повідомить про помилку: «Потрібно реалізувати метод».
Універсальний інтерфейс
Код, який працює з масивом або списком абстрактного типу (Employee[], List<Animal>), може бути цілком універсальним. Ви можете додавати нові підкласи — і основний код змінювати не доведеться.
Захист від випадкових обʼєктів
Оскільки абстрактний клас не можна створити безпосередньо, ніхто не зможе випадково створити обʼєкт «незрозумілого» типу, який не реалізує потрібні методи.
Теорія та синтаксис: як оголосити абстрактний клас і метод
- Абстрактний клас оголошується за допомогою ключового слова abstract перед class.
- Абстрактний метод оголошується за допомогою ключового слова abstract і не має тіла (лише крапка з комою).
- Клас із принаймні одним абстрактним методом має бути абстрактним.
- Клас, що успадковує абстрактний клас, зобовʼязаний реалізувати всі його абстрактні методи або сам має бути абстрактним.
Схема
public abstract class Animal {
public abstract void makeSound();
}
public class Cat extends Animal {
@Override
public void makeSound() {
System.out.println("Няв!");
}
}
5. Типові помилки під час роботи з абстрактними класами
Помилка № 1: спроба створити обʼєкт абстрактного класу.
new Animal() — такий код не скомпілюється. Абстрактні класи — це як інструкція зі складання меблів без самих деталей: доки не зʼявиться конкретний підклас, зібрати обʼєкт не можна.
Помилка № 2: забули реалізувати абстрактний метод у підкласі.
Якщо ви оголосили абстрактний метод, але не реалізували його в нащадку (і не зробили клас також абстрактним), компілятор повідомить про помилку.
Помилка № 3: забули про модифікатори доступу.
Перевизначений метод не може мати суворіший модифікатор доступу, ніж у базовому класі. Наприклад, якщо абстрактний метод був public, то і реалізація має бути public (а не protected чи private).
Помилка № 4: спроба оголосити абстрактний метод із тілом.
abstract-метод не може мати тіло, інакше компілятор закотить очі та скаже: «Визначись уже — або абстракція, або реалізація!»
Помилка № 5: поліморфізм не працює для статичних методів.
Поліморфізм працює лише для нестатичних методів. Статичні методи не перевизначаються — вони приховуються, тому поведінка під час виклику залежить від типу змінної, а не від фактичного обʼєкта.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ