В чем разница между абстрактным классом и интерфейсом в современном ООП (Java 8+)?

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

Ответ

Абстрактные классы не устарели, но их роль изменилась с появлением интерфейсов с default-методами (Java 8). Выбор зависит от проектируемых отношений.

Критерий Абстрактный класс Интерфейс (с default-методами)
Наследование Один класс (единичное) Множество интерфейсов
Состояние (поля) ✅ Может иметь поля экземпляра с модификаторами доступа ❌ Только public static final константы
Конструктор ✅ Есть ❌ Нет
Реализация методов ✅ Полная и абстрактная реализация ✅ Только default и static методы (Java 8+), иначе абстрактные
Цель ОБЩАЯ ЛОГИКА И СОСТОЯНИЕ для близкородственных объектов. Выражает отношение "IS-A". ОБЩИЙ КОНТРАКТ для разнородных объектов. Выражает отношение "CAN-DO" (роль, способность).

Когда использовать абстрактный класс:

  • Несколько классов имеют общее состояние и поведение, которое можно вынести в базовую реализацию.
  • Нужен не-public конструктор (например, для паттерна Строитель).
  • Планируется добавлять protected или package-private методы для наследников.

Когда использовать интерфейс:

  • Нужна множественная реализация разных контрактов.
  • Классы, реализующие интерфейс, не связаны иерархией наследования (например, Car и Clock могут реализовывать Audible).
  • Определяется API для библиотек или слоев (Service, Repository).
  • Работа с функциональными интерфейсами и лямбда-выражениями.

Пример гибридного подхода:

// Интерфейс определяет основную способность
interface Transport {
    void move();
    default void honk() { System.out.println("Beep!"); } // Дефолтная реализация
}

// Абстрактный класс предоставляет общую логику для наземного транспорта
abstract class GroundTransport implements Transport {
    protected int wheels; // Общее состояние
    public GroundTransport(int wheels) { this.wheels = wheels; } // Конструктор
    @Override
    public void move() { System.out.println("Moving on " + wheels + " wheels"); } // Общая реализация
}

// Конкретные классы
class Car extends GroundTransport {
    public Car() { super(4); }
}
class Bike extends GroundTransport {
    public Bike() { super(2); }
}