Абстрактные классы и интерфейсы
После наследования и полиморфизма возникает важный вопрос: как описать общий тип, если часть поведения уже можно реализовать, а часть нужно обязательно оставить конкретным классам? Для этого в Java есть абстрактные классы и интерфейсы. Они помогают задавать общие правила для разных реализаций, но делают это по-разному.
Абстрактный класс — основа для семейства классов
Абстрактный класс объявляется с помощью abstract.
Создать его объект напрямую нельзя, зато он может хранить состояние,
иметь конструкторы, обычные методы и методы без реализации — абстрактные методы.
abstract class Animal {
private String name;
Animal(String name) {
this.name = name;
}
String getName() {
return name;
}
abstract String sound();
}
class Dog extends Animal {
Dog(String name) {
super(name);
}
@Override
String sound() {
return "Woof";
}
}
У Animal уже есть общее состояние и готовый метод
getName(), но конкретный звук заранее неизвестен.
Поэтому sound() только задаёт требование,
а Dog предоставляет реализацию.
Интерфейс — контракт возможностей
Интерфейс удобно использовать, когда важно описать не общее происхождение объектов,
а то, что они умеют делать. Класс подключает интерфейс через
implements и берёт на себя обязанность выполнить его контракт.
interface Payable {
double calculatePayment();
}
class Freelancer implements Payable {
private double amount;
Freelancer(double amount) {
this.amount = amount;
}
@Override
public double calculatePayment() {
return amount;
}
}
Метод calculatePayment() в интерфейсе объявлен без тела.
Такой метод неявно является public abstract,
поэтому реализация в классе должна быть public.
Сам интерфейс, как и абстрактный класс, нельзя создать через new.
Главное преимущество интерфейса особенно заметно, когда одному классу нужны несколько независимых возможностей. Класс может наследоваться только от одного класса, но реализовывать несколько интерфейсов.
interface Printable {
void print();
}
interface Scannable {
void scan();
}
class OfficeDevice implements Printable, Scannable {
@Override
public void print() {
System.out.println("Printing");
}
@Override
public void scan() {
System.out.println("Scanning");
}
}
Интерфейс может содержать готовое поведение
Интерфейс — это не обязательно набор методов без тела.
Метод с модификатором default может иметь реализацию,
которую классы получат автоматически и при необходимости смогут переопределить.
interface Loggable {
default void log(String message) {
System.out.println(message);
}
}
Это позволяет добавлять общее поведение к контракту, не превращая интерфейс в базовый класс. При этом интерфейс по-прежнему не предназначен для хранения обычного состояния каждого объекта.
Абстрактный класс или интерфейс?
Ориентируйся на смысл связи между классами. Абстрактный класс подходит, когда у родственных объектов есть общая основа: состояние, конструкторы и часть общей реализации. Интерфейс подходит, когда нужно описать отдельную возможность или контракт, который могут выполнять даже классы из разных иерархий.
- Абстрактный класс: «эти объекты относятся к одному семейству и делят общую основу».
- Интерфейс: «эти объекты обязуются уметь делать определённую вещь».
Эти подходы не конкурируют друг с другом. Один класс может наследоваться от абстрактного класса и одновременно реализовывать один или несколько интерфейсов — именно так часто и строятся гибкие модели в Java.