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

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

Ответ

Абстрактный класс следует выбирать, когда необходимо:

  1. Предоставить общую реализацию (код) для группы тесно связанных классов. Абстрактный класс может содержать поля, конструкторы и реализованные методы, которые будут унаследованы.
  2. Объявить нестатические и нефинальные поля, чтобы подклассы могли иметь и модифицировать общее состояние.
  3. Определить общий шаблон (template method), где часть алгоритма фиксирована, а часть делегирована абстрактным методам подклассов.

Ключевое отличие от интерфейса (до Java 8): абстрактный класс описывает «is-a» отношение (наследование), а интерфейс — «capable-of» (реализация контракта). С появлением default-методов границы размылись, но абстрактный класс по-прежнему необходим для общего состояния.

Пример:

abstract class DataExporter {
    protected String dataSource;

    public DataExporter(String source) {
        this.dataSource = source;
    }

    // Общая реализация для всех экспортёров
    public final void export() {
        String data = fetchData();
        String formatted = format(data);
        save(formatted);
    }

    protected abstract String fetchData();
    protected abstract String format(String rawData);

    private void save(String data) {
        System.out.println("Saving: " + data);
    }
}

class CsvExporter extends DataExporter {
    public CsvExporter(String source) { super(source); }

    @Override
    protected String fetchData() { return "data from " + dataSource; }

    @Override
    protected String format(String rawData) { 
        return rawData + " formatted as CSV"; 
    }
}