Абстрактный класс и интерфейс: в чём отличие

Абстрактный класс и интерфейс - два способа описать обобщение в объектно-ориентированном программировании, и студенты постоянно их путают. Оба нельзя инстанцировать напрямую, оба задают, что должны уметь наследники, оба участвуют в полиморфизме. Но смысл у них разный: абстрактный класс отвечает на вопрос «чем объект является», а интерфейс - «что объект умеет делать». Ниже разберём отличие по пунктам, посмотрим на код в Java, C# и Python и подберём правило выбора. Чтобы быстро сравнить два варианта для вашей конкретной иерархии, соберите запрос в форме ниже.
Что такое абстрактный класс
Абстрактный класс - это класс, который объявлен как неполный: он содержит хотя бы один абстрактный метод (без реализации) и поэтому не может быть инстанцирован. Его задача - собрать общее состояние и общее поведение для группы родственных классов, а часть методов оставить на доделку наследникам.
Ключевое слово здесь - общее. Абстрактный класс хранит поля, конструктор, готовые методы. Наследник получает всё это «по наследству» и дописывает только то, что специфично для него.
abstract class Figure {
private final String color; // общее состояние
Figure(String color) { this.color = color; }
String describe() { // готовый метод
return color + " фигура площадью " + area();
}
abstract double area(); // контракт для наследника
}
class Circle extends Figure {
private final double r;
Circle(String color, double r) { super(color); this.r = r; }
@Override double area() { return Math.PI * r * r; }
}
Figure нельзя создать через new Figure(...) - он абстрактный. Зато Circle наследует поле цвета, конструктор и готовый describe(), а реализует только area(). Это отношение «является» (is-a): круг является фигурой.
Что такое интерфейс
Интерфейс - это чистый контракт: список методов, которые класс обязуется реализовать, без собственного состояния. Он не описывает, чем объект является, а только декларирует способность. Класс, реализующий интерфейс, как бы подписывает соглашение: «я умею всё, что здесь перечислено».

interface Drawable {
void draw(); // только сигнатура, без тела
}
class Circle extends Figure implements Drawable {
// ...
@Override public void draw() { /* рисуем круг */ }
}
Circle одновременно является фигурой (наследует абстрактный класс) и умеет рисоваться (реализует интерфейс). Это отношение «умеет» (can-do): способность, которую может иметь и совсем неродственный класс - например, Button implements Drawable, хотя кнопка не фигура.
Главное отличие: «является» против «умеет»
Самый надёжный критерий выбора - семантика отношения, а не технические детали:
- Абстрактный класс моделирует природу объекта, иерархию is-a.
DogявляетсяAnimal. У наследников общая суть и часто общий код. - Интерфейс моделирует роль или способность, can-do.
BirdиPlaneоба умеютFly, хотя в иерархии живых существ не пересекаются.
Из этого вытекает практическое правило. Если несколько классов разделяют не только сигнатуры методов, но и реальную реализацию и состояние - это кандидат на абстрактный класс. Если же объединяет только способность, которая встречается у разнородных классов, - это интерфейс. Эта пара хорошо ложится на три кита ООП - инкапсуляцию, наследование и полиморфизм.
Множественное наследование и состояние
Техническое отличие, которое в большинстве языков решает всё:
- Класс наследует только один класс (в Java, C#, Python с одним базовым по C3-линеаризации обычно один основной). Поэтому абстрактных родителей не может быть несколько.
- Интерфейсов класс реализует сколько угодно. Один объект легко комбинирует роли:
Comparable,Serializable,Drawableсразу.
Второе отличие - состояние. Абстрактный класс хранит поля экземпляра и имеет конструктор. Классический интерфейс полей экземпляра не имеет (только константы) и конструктора не вызывает - у него нет состояния для инициализации.

Современные интерфейсы: грань размылась
Раньше отличие было резким: абстрактный класс может иметь реализованные методы, интерфейс - нет. Сегодня граница тоньше:
- Java 8+ разрешает в интерфейсе
default-методы с телом иstatic-методы. Интерфейс может нести готовое поведение, но всё равно не имеет состояния экземпляра. - C# 8+ ввёл default interface methods - то же самое.
- Python не различает их синтаксически: абстракция делается через модуль
abc(ABC+@abstractmethod), а «интерфейсы» эмулируются абстрактными классами илиProtocolизtypingдля структурной типизации.
from abc import ABC, abstractmethod
class Figure(ABC):
def __init__(self, color): self.color = color # состояние есть
@abstractmethod
def area(self): ... # абстрактный метод
Несмотря на default-методы, концептуальное отличие осталось прежним: абстрактный класс - это «является» с общим состоянием, интерфейс - «умеет» без состояния и с множественной реализацией. Технические совпадения не отменяют разный замысел.
Как выбрать: чек-лист
Короткий алгоритм для экзамена и для практики:
- Наследники разделяют общий код и поля? → абстрактный класс.
- Способность нужна разнородным классам из разных иерархий? → интерфейс.
- Объекту нужно несколько ролей сразу? → только интерфейсы.
- Нужно зафиксировать чистый контракт без реализации для слабой связанности (API, плагины, тесты-моки)? → интерфейс.
- Сомневаешься? Начни с интерфейса - он гибче и не сжигает единственное наследование.
Частые ошибки
- «Интерфейс - это просто абстрактный класс без полей». Это техническое упрощение, которое скрывает смысл: интерфейс про роль, абстрактный класс про природу. Из-за подмены студенты выбирают наследование там, где нужна композиция ролей.
- Запихивать состояние в интерфейс через костыли. Если способность требует общих полей и сложной реализации - это сигнал, что нужен абстрактный класс или композиция, а не интерфейс с default-методами на все случаи.
- Глубокая иерархия абстрактных классов. Цепочка
A → B → C → Dхрупка: изменение в корне ломает всё. Часто это признак, что роли надо было вынести в интерфейсы. - Путать с обычным наследованием. Не каждый базовый класс абстрактен. Абстрактным он становится только при наличии нереализованного метода или явного запрета инстанцирования.
- Думать, что default-методы стёрли разницу. Синтаксис сблизился, замысел - нет. Состояние и одиночное наследование по-прежнему отличают абстрактный класс.
FAQ
Можно ли в интерфейсе написать конструктор?
Нет. Интерфейс не имеет состояния экземпляра, инициализировать нечего, поэтому конструктора у него нет. Конструктор есть только у абстрактного класса - он вызывается через super(...) из наследника для инициализации общих полей.
Что выбрать, если нужно и общее состояние, и несколько ролей?
Комбинируй: один абстрактный класс как основа иерархии плюс несколько интерфейсов для дополнительных способностей. class Circle extends Figure implements Drawable, Comparable - нормальная и частая конструкция.
Зачем интерфейс, если есть абстрактный класс с пустыми методами? Ради гибкости и слабой связанности. Класс может реализовать много интерфейсов, но наследовать лишь один абстрактный класс. Интерфейс не навязывает иерархию и не тратит единственное наследование, поэтому для контрактов API и тестовых моков он предпочтительнее.
Коротко
Абстрактный класс задаёт, чем объект является: общая природа, общее состояние, общий код, одиночное наследование. Интерфейс задаёт, что объект умеет: чистый контракт без состояния, множественная реализация, роли для разнородных классов. Default-методы сблизили синтаксис, но не замысел. Правило выбора: общий код и is-a - абстрактный класс; способность для разных иерархий и несколько ролей сразу - интерфейс; сомневаешься - начни с интерфейса.
Читайте также

Наследование классов в ООП: пример и разбор
Наследование классов в ООП на примере: базовый класс, наследник, переопределение методов и вызов super. Разбираем виды наследования, синтаксис в разных языках и частые ошибки.

Полиморфизм в ООП: пример на простом коде и разбор
Полиморфизм в ООП на примере: один вызов метода работает по-разному для разных классов. Разбираем перегрузку, переопределение, виртуальные методы и утиную типизацию на коде.

Порождающие, структурные и поведенческие паттерны GoF
Чем отличаются порождающие, структурные и поведенческие паттерны проектирования, как разнести 23 шаблона GoF по группам и когда какой применять с примерами.