Абстрактные классы

Определение

Абстрактный класс нельзя создать напрямую, но он может хранить состояние, иметь конструкторы и объединять общее поведение семейства подтипов.

abstract class Importer {
    private final Clock clock;
 
    protected Importer(Clock clock) {
        this.clock = clock;
    }
 
    public final Result run(Input input) {
        validate(input);
        Data data = parse(input);
        return new Result(data, clock.instant());
    }
 
    protected void validate(Input input) {
        Objects.requireNonNull(input);
    }
 
    protected abstract Data parse(Input input);
}

Абстрактный метод объявляет контракт без тела. Конкретный наследник обязан реализовать его либо сам остаться абстрактным.

Что может содержать abstract class

  • поля экземпляра и статические поля;
  • конструкторы;
  • обычные, final, static и abstract-методы;
  • вложенные типы;
  • любой допустимый модификатор доступа для членов.

Абстрактный класс может не иметь абстрактных методов: тогда запрет создания экземпляра сам является частью дизайна.

Абстрактный метод не может быть private, static или final: его нужно увидеть и переопределить в экземпляре наследника.

Template Method

В примере run фиксирует общий алгоритм, а parse оставляет изменяемый шаг. final защищает порядок операций:

final class CsvImporter extends Importer {
    CsvImporter(Clock clock) {
        super(clock);
    }
 
    @Override
    protected Data parse(Input input) {
        return parseCsv(input);
    }
}

Такой шаблон удобен, когда общее состояние и жизненный цикл действительно принадлежат базовому классу. Для независимых стратегий композиция с интерфейсом обычно проще.

Абстрактный класс и интерфейс

Абстрактный классИнтерфейс
один базовый классможно реализовать несколько интерфейсов
поля состояния экземпляранет обычного состояния экземпляра
конструкторыконструкторов нет
любые модификаторы методовметоды контракта публичны; возможны default/static/private helpers
общий код и состояниероль/способность и слабая связь

Интерфейс тоже может иметь реализацию default, поэтому различие не сводится к фразе «в интерфейсе нет кода».

Ловушки

  • Объявлять abstract-метод с телом.
  • Пытаться совместить abstract с final, private или static.
  • Выносить в базовый класс случайно похожие поля несвязанных сущностей.
  • Открывать protected mutable-поля вместо защищённых операций.
  • Использовать наследование там, где нужна заменяемая стратегия.
  • Вызывать абстрактный/переопределяемый метод из конструктора.

Вопросы на собеседовании

  1. Может ли абстрактный класс не содержать абстрактных методов?
  2. Может ли у него быть конструктор и зачем?
  3. Какие модификаторы несовместимы с abstract-методом?
  4. Чем абстрактный класс отличается от современного интерфейса?
  5. Что такое Template Method?
  6. Когда лучше выбрать композицию с интерфейсом?

Связанные заметки

Основы ООП · Наследование · Интерфейсы Java · Переопределение Java · Конструкторы Java