1. Абстрактний клас: пригадаємо основи
Перш ніж перейти до порівняння, згадаємо, що таке абстрактний клас.
Абстрактний клас — це клас, який не можна створити безпосередньо (не можна написати new Animal()), але він може містити як звичайні (з реалізацією), так і абстрактні (без реалізації) методи. Абстрактний клас часто використовують як основу для інших класів, які наслідують його поведінку та/або зобовʼязані реалізувати деякі методи.
Наприклад, якщо у нашому застосунку є різні види транспорту, можна створити абстрактний клас Transport:
public abstract class Transport {
private String model;
public Transport(String model) {
this.model = model;
}
public String getModel() {
return model;
}
// Абстрактний метод — реалізації немає, лише оголошення
public abstract void move();
// Звичайний метод — реалізація є
public void printInfo() {
System.out.println("Модель транспорту: " + model);
}
}
Особливості абстрактного класу:
- Може містити поля (стан).
- Може містити реалізовані методи.
- Може містити абстрактні методи (обов’язкові до реалізації в нащадках).
- Не можна створити екземпляр безпосередньо.
- Використовується для спільної поведінки та стану.
2. Інтерфейс: пригадаємо основи
Інтерфейс — це набір методів, які має реалізувати клас. Інтерфейс не описує стан (не може мати звичайних полів, лише константи) і не містив реалізацій методів до Java 8. Інтерфейс — це чистий контракт: «Якщо ви мене реалізуєте, ви зобовʼязані вміти робити ось це».
Приклад інтерфейсу:
public interface Movable {
void move(int x, int y);
}
Особливості інтерфейсу:
- Не містить стану (лише public static final константи).
- До Java 8 — лише абстрактні методи (починаючи з Java 8, зʼявилися default- і static-методи, але про них пізніше).
- Методи за замовчуванням завжди public abstract.
- Клас може реалізовувати кілька інтерфейсів.
- Використовується для опису можливостей, «що вміє робити» клас.
3. Таблиця порівняння: абстрактний клас vs інтерфейс
Час порівняти ці два інструменти віч-на-віч! Ось наочна таблиця:
| Особливість | Абстрактний клас | Інтерфейс |
|---|---|---|
| Синтаксис | |
|
| Можна створювати екземпляр? | Ні | Ні |
| Може містити звичайні методи? | Так | До Java 8 — ні, з Java 8 — лише default/static |
| Може містити абстрактні методи? | Так | Так (усі методи до Java 8 — абстрактні) |
| Може містити поля (стан)? | Так (будь-які поля) | Лише public static final (константи) |
| Може містити конструктори? | Так | Ні |
| Наслідування | Можна наслідувати лише один клас | Можна реалізувати кілька інтерфейсів |
| Модифікатори методів | Будь-які (public, protected, private) | Методи за замовчуванням public abstract. Починаючи з Java 9, можна додавати private методи для використання всередині інтерфейсу. |
| Наслідування через | |
|
| Для чого зазвичай використовується | Спільна реалізація та стан | Опис можливостей і ролей |
| Приклади з JDK | |
|
4. Коли використовувати інтерфейс, а коли абстрактний клас?
Використовуйте інтерфейс, коли:
- Ви хочете описати «що вміє робити» клас, не переймаючись тим, як він це робить.
- Вам потрібно, щоб клас міг реалізувати кілька незалежних можливостей.
- Приклад: Comparable (можна порівнювати), Serializable (можна серіалізувати), Runnable (можна запускати у потоці).
Використовуйте абстрактний клас, коли:
- Ви хочете задати спільну реалізацію та стан, які будуть у всіх нащадків.
- Вам потрібно, щоб усі нащадки мали певні поля або методи з реалізацією.
- Наслідування — суворо «один до одного»: клас може наслідувати лише один клас (звичайний або абстрактний).
Життєві аналогії
- Інтерфейс — це як «водійські права»: якщо вони у вас є, ви можете керувати автомобілем, але ніхто не каже, на якій саме машині і як саме ви це робите.
- Абстрактний клас — це як «загальне креслення автомобіля»: у всіх машин є кермо, педалі, двигун, але кожна марка реалізує деталі по-своєму.
5. Приклади зі стандартної бібліотеки Java
Інтерфейс: Comparable
public interface Comparable<T> {
int compareTo(T o);
}
Будь-який клас, який реалізує цей інтерфейс, зобовʼязаний реалізувати метод compareTo. Наприклад, String, Integer, LocalDate та багато інших.
Абстрактний клас: AbstractList
public abstract class AbstractList<E> implements List<E> {
// Реалізація деяких методів List за замовчуванням
// Деякі методи залишено абстрактними
}
AbstractList уже реалізує частину поведінки колекцій (наприклад, методи додавання/видалення), але залишає деякі методи абстрактними, щоб нащадки могли реалізувати їх по-своєму.
6. Приклади коду: порівняння на практиці
Інтерфейс
Створимо інтерфейс і клас, який його реалізує.
public interface Printable {
void print();
}
public class Document implements Printable {
@Override
public void print() {
System.out.println("Друкую документ...");
}
}
Абстрактний клас
Тепер абстрактний клас і його нащадок.
public abstract class Machine {
public void turnOn() {
System.out.println("Машина увімкнена.");
}
public abstract void work();
}
public class Printer extends Machine {
@Override
public void work() {
System.out.println("Принтер друкує...");
}
}
Клас реалізує обидва: і інтерфейс, і абстрактний клас
public class SmartPrinter extends Machine implements Printable {
@Override
public void work() {
System.out.println("Розумний принтер працює...");
}
@Override
public void print() {
System.out.println("Розумний принтер друкує...");
}
}
7. Множинна реалізація інтерфейсів: чому це зручно
У Java клас може наслідувати лише один клас (абстрактний або звичайний), але реалізовувати скільки завгодно інтерфейсів! Це дозволяє створювати гнучкі, розширювані архітектури.
public interface Scannable {
void scan();
}
public class MultiFunctionPrinter extends Machine implements Printable, Scannable {
@Override
public void work() {
System.out.println("БФП працює...");
}
@Override
public void print() {
System.out.println("БФП друкує...");
}
@Override
public void scan() {
System.out.println("БФП сканує...");
}
}
Коли що обирати?
- Якщо ви проєктуєте базовий функціонал із спільним станом (наприклад, поля), використовуйте абстрактний клас.
- Якщо ви хочете додати обʼєктам «ярлики можливостей» (наприклад, «уміє друкувати», «уміє порівнюватися», «уміє серіалізуватися») — використовуйте інтерфейси.
- Якщо ви не впевнені — починайте з інтерфейсу. У Java це вважають гарним тоном: інтерфейси дають більшу гнучкість і розширюваність.
8. Типові помилки та підводні камені
Помилка № 1: намагаєтеся наслідувати кілька класів — Java цього не дозволить!
Клас може наслідувати лише один клас, але реалізовувати багато інтерфейсів. Наприклад, class A extends B, C — помилка, а от class A extends B implements X, Y, Z — будь ласка.
Помилка № 2: плутаєте поля інтерфейсу та класу.
В інтерфейсі можна оголошувати лише константи (public static final). Не можна оголосити звичайний стан, наприклад, private int count; — компілятор одразу вас зупинить.
Помилка № 3: не реалізували всі методи інтерфейсу.
Якщо клас не реалізує бодай один метод інтерфейсу — його слід оголосити як abstract, інакше компілятор видасть помилку.
Помилка № 4: намагаєтеся створити екземпляр інтерфейсу або абстрактного класу.
Обидва ці типи — «напівфабрикати». Їх можна лише наслідувати/реалізовувати, але не можна створити безпосередньо:
Printable p = new Printable(); // Помилка!
Machine m = new Machine(); // Помилка!
Помилка № 5: думаєте, що інтерфейс може мати конструктор.
Інтерфейси не можуть мати конструкторів, оскільки вони не описують стан обʼєктів. Це можливо лише у класів (звичайних і абстрактних).
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ