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 класс может наследовать только один класс (abstract или обычный), но реализовывать сколько угодно интерфейсов! Это позволяет создавать гибкие, расширяемые архитектуры.
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: Думаете, что интерфейс может иметь конструктор.
У интерфейсов не может быть конструкторов, так как они не описывают состояние объектов. Только у классов (обычных и абстрактных).
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ