Когда следует использовать абстрактный класс, а когда интерфейс в Java?

«Когда следует использовать абстрактный класс, а когда интерфейс в Java?» — вопрос из категории ООП, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Выбор между абстрактным классом и интерфейсом зависит от проектируемой абстракции.

Используйте абстрактный класс, когда:

  1. Нужно разделить общее состояние (поля) между несколькими родственными классами.
  2. Требуется реализовать нестатический/нефинальный метод с общей логикой для всех наследников (шаблонный метод).
  3. Необходим конструктор для инициализации общего состояния.
  4. Нужны модификаторы доступа protected или package-private для методов или полей.

Пример абстрактного класса (шаблонный метод):

public abstract class ReportGenerator {
    // Общее состояние
    protected String reportName;

    // Шаблонный метод (определяет скелет алгоритма)
    public final void generateReport() {
        fetchData();
        processData(); // Абстрактный шаг
        formatReport(); // Абстрактный шаг
        saveReport();   // Общая реализация
    }

    protected abstract void processData();
    protected abstract void formatReport();

    private void saveReport() {
        System.out.println("Отчет '" + reportName + "' сохранен.");
    }
}

Используйте интерфейс, когда:

  1. Нужно определить контракт (поведение) для несвязанных классов. Классы Car и Plane могут реализовывать Movable.
  2. Требуется множественное "наследование" типов (класс может реализовать много интерфейсов).
  3. Хотите добавить функциональность через default-методы (начиная с Java 8), не ломая существующие реализации.
  4. Создаете API или библиотеку, где реализация скрыта.

Современная практика:

  • Предпочитайте интерфейсы для определения типов (с default-методами для общей логики).
  • Используйте абстрактные классы реже, только когда действительно необходимо наследование состояния или строгий контроль структуры алгоритма.