Главная › Блог › Интерфейс или абстрактный класс: чем отличаются и что выбрать

Интерфейс или абстрактный класс: чем отличаются и что выбрать

Суть вопроса

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

Абстрактный класс — это черновик договора с частично заполненными пунктами: часть текста уже написана и обязательна, часть — оставлена на подпись наследнику. Интерфейс — это просто список требований без единого готового пункта, раньше вообще без реализации: «умеешь плавать — предоставь метод swim()», а как ты это делаешь, никого не касается.

Ключевые технические отличия

  • Наследование: класс может наследовать только один абстрактный класс (одиночное наследование), но реализовать сколько угодно интерфейсов.
  • Состояние: абстрактный класс может иметь поля с состоянием и конструктор. Интерфейс — только константы (public static final) и с Java 9 — private методы для внутренней переиспользуемой логики, но никакого instance-состояния.
  • Методы: в абстрактном классе можно смешивать реализованные и абстрактные методы. В интерфейсе с Java 8 появились default и static методы с телом, но это не отменяет того, что интерфейс не хранит состояние.
  • Модификаторы доступа: методы абстрактного класса могут быть private, protected, public. Методы интерфейса по умолчанию public (кроме private-методов для внутреннего использования).
  • Конструктор: у абстрактного класса есть конструктор, вызываемый через super() из наследника. У интерфейса конструктора нет и быть не может — это не часть иерархии инициализации объекта.

Default-методы и diamond problem

После Java 8 граница стёрлась сильнее, чем кажется на первый взгляд, и это любимая ловушка на собесе: «а если интерфейс может иметь реализацию, зачем вообще абстрактные классы?». Ответ — из-за состояния и одиночного наследования классов. Default-методы решают проблему эволюции API (можно добавить метод в интерфейс без поломки всех реализаций), но не решают задачу хранения общих полей.

Отдельно стоит вопрос про конфликт default-методов при множественном наследовании интерфейсов:

interface A {
    default String greet() { return "A"; }
}

interface B {
    default String greet() { return "B"; }
}

class C implements A, B {
    // обязаны переопределить, иначе ошибка компиляции
    public String greet() {
        return A.super.greet() + B.super.greet();
    }
}

Java не выбирает "победителя" сама — компилятор требует явного разрешения конфликта. Это осознанное решение авторов языка: неявный diamond problem, как в C++, был бы источником трудноуловимых багов.

Частая ошибка — говорить «интерфейсы теперь как абстрактные классы, раз у них есть реализация». Нет: у интерфейса до сих пор нет полей состояния, конструктора и возможности изменить видимость метода за пределами public/private. Default-методы — это про контракт с эволюцией, а не про хранение данных.

Как выбирать на практике

Абстрактный класс подходит, когда у наследников есть общее состояние и общая часть логики, которую бессмысленно копировать (шаблонный метод, Template Method паттерн). Интерфейс — когда важен именно контракт поведения, который могут реализовать совершенно разные по природе классы: Comparable, Runnable, Serializable — у File и у Thread нет ничего общего по иерархии, но оба могут быть Runnable/Comparable.

Практическое правило, которое хорошо звучит на собеседовании: если сомневаешься — начинай с интерфейса. Java поддерживает только одиночное наследование классов, и жёсткая привязка к абстрактному классу ограничивает наследника навсегда. Интерфейсов можно реализовать сколько угодно, это дешевле для будущей расширяемости.

Может ли интерфейс иметь состояние через static поля?
Может иметь только public static final константы — то есть неизменяемое состояние на уровне типа, а не instance-состояние объекта. Изменяемых static-полей с логикой в интерфейсах не бывает.
Зачем в Java 9 добавили private-методы в интерфейсы?
Чтобы default и static методы могли переиспользовать общий код без дублирования, не раскрывая его наружу как часть публичного контракта.
Что происходит, если абстрактный класс реализует интерфейс, но не переопределяет все методы?
Это разрешено — абстрактный класс может оставить часть методов интерфейса нереализованными, они остаются абстрактными, и обязанность реализовать их переходит к конкретному наследнику.

Ещё разборы