В чем разница между интерфейсом и абстрактным классом в Java и когда что использовать?

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

Ответ

Интерфейс определяет контракт (что объект умеет делать), а абстрактный класс — частичную реализацию для родственных объектов (что объект есть и как частично работает).

Критерий Интерфейс (до Java 8+) Абстрактный класс
Состояние Только static final константы Может иметь поля (состояние)
Конструкторы Нет Есть
Реализация методов default и static методы (Java 8+) Полная или частичная (abstract)
Наследование Класс может реализовать много интерфейсов Класс может наследовать только один абстрактный класс

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

  1. Для определения полиморфного поведения, не связанного с иерархией классов (например, Comparable, Serializable).
  2. Когда несколько несвязанных классов должны реализовать один и тот же контракт.
  3. Для достижения слабой связанности и тестируемости (внедрение зависимостей через интерфейс).

Пример интерфейса:

public interface PaymentProcessor {
    // Контракт
    PaymentResult process(PaymentRequest request);
    // Реализация по умолчанию (Java 8+)
    default boolean validate(PaymentRequest request) {
        return request.getAmount() > 0;
    }
}

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

  1. Когда несколько тесно связанных классов имеют общую логику или состояние.
  2. Для предоставления шаблонного метода, определяющего скелет алгоритма.
  3. Для контроля над расширением класса (поскольку наследование — единственное).

Пример абстрактного класса:

public abstract class DataExporter {
    // Общее состояние
    protected String outputFileName;
    // Конструктор
    public DataExporter(String fileName) { this.outputFileName = fileName; }
    // Частичная реализация
    public final void export() {
        prepareData();
        generateFile(); // Абстрактный шаг
        cleanup();
    }
    protected abstract void generateFile();
}