Абстрактный класс в Java — это класс, объявленный с модификатором abstract: создать его объект через new нельзя, он служит общим родителем для подклассов. В отличие от интерфейса, абстрактный класс может хранить состояние (обычные поля), иметь конструкторы и уже реализованные методы, а часть поведения оставлять подклассам в виде абстрактных методов.
Содержание
Ниже — рабочий пример, правила ключевого слова abstract, реальные ошибки компилятора, подводный камень с конструктором и таблица «абстрактный класс или интерфейс» с учетом default-методов и sealed. Все примеры проверены на OpenJDK 25.0.4 (Java 25 LTS) 24.09.2026.
Три термина, которые путают
- Абстрактный класс — класс с
abstractв объявлении. Может содержать ноль или больше абстрактных методов. - Абстрактный метод — метод с
abstractи без тела: сигнатура и точка с запятой вместо фигурных скобок. Он обещает поведение, но не реализует его. - Интерфейс — отдельный вид типа (
interface). Описывает контракт; с Java 8 может содержать default- и static-методы, с Java 9 — private-методы, но не хранит состояние объекта.
Связь такая: абстрактный метод может быть только в абстрактном классе (или в интерфейсе), а вот абстрактный класс не обязан иметь абстрактные методы. Особый случай — enum: слово abstract к нему писать нельзя, но абстрактный метод объявить можно, если каждая константа реализует его в своем теле (PLUS { int apply(int a, int b) { return a + b; } }); такой enum компилятор сам делает абстрактным.
Минимальный рабочий пример
Аналогия: абстрактный класс — это бланк с уже напечатанной шапкой и пустыми полями, которые обязан заполнить каждый, кто его использует. Шапка — общий код, пустые поля — абстрактные методы.
abstract class Shape {
private final String name;
protected Shape(String name) {
this.name = name;
}
public abstract double area();
public String describe() {
return name + ": площадь " + String.format("%.2f", area());
}
}
class Circle extends Shape {
private final double r;
Circle(double r) {
super("Круг");
this.r = r;
}
@Override
public double area() {
return Math.PI * r * r;
}
}
class Rectangle extends Shape {
private final double w, h;
Rectangle(double w, double h) {
super("Прямоугольник");
this.w = w;
this.h = h;
}
@Override
public double area() {
return w * h;
}
}
public class Main {
public static void main(String[] args) {
Shape[] shapes = { new Circle(1.5), new Rectangle(2, 3) };
for (Shape s : shapes) {
System.out.println(s.describe());
}
}
}
Сохраните все в файл Main.java, выполните javac Main.java и java Main. В консоли появятся две строки:
Круг: площадь 7.07
Прямоугольник: площадь 6.00
Разбор по частям:
abstract class Shape— объектShapeсоздать нельзя, но переменная типаShapeможет ссылаться наCircleилиRectangle.private final String name— состояние, общее для всех фигур. Интерфейс так не умеет.protected Shape(String name)— конструктор есть, его вызывают подклассы черезsuper(...).public abstract double area();— абстрактный метод: тела нет, каждый подкласс обязан его реализовать.describe()— готовый метод, который вызывает еще не написанныйarea(). Какую реализацию выбрать, JVM решает во время выполнения по реальному типу объекта.
Правила ключевого слова abstract
| Ситуация | Можно? | Что будет |
|---|---|---|
new абстрактного класса |
Нет | ошибка компиляции is abstract; cannot be instantiated |
| Абстрактный метод в обычном классе | Нет | класс придется пометить abstract |
| Абстрактный класс без абстрактных методов | Да | так запрещают создание объектов базового типа |
Поля, конструкторы, static-методы, main |
Да | работают как в обычном классе |
abstract + private / static / final у метода |
Нет | illegal combination of modifiers |
abstract + final у класса |
Нет | illegal combination of modifiers: abstract and final — final запрещает наследование, а без него класс бесполезен |
| Подкласс реализовал не все абстрактные методы | Только если сам abstract |
иначе ошибка компиляции |
Анонимный подкласс new Base() { ... } |
Да | создается объект подкласса, а не самого Base |
Почему запрещены комбинации: private-метод не виден подклассу, значит его нельзя переопределить; static-метод принадлежит классу и не переопределяется полиморфно; final прямо запрещает переопределение. Абстрактный метод без переопределения теряет смысл.
Типичные ошибки компилятора
Ошибка 1. Попытка создать объект абстрактного класса.
abstract class Shape {
public abstract double area();
}
public class Main {
public static void main(String[] args) {
Shape s = new Shape();
}
}
Результат javac (JDK 25):
Main.java:7: error: Shape is abstract; cannot be instantiated
Shape s = new Shape();
^
1 error
Исправление: создавать объект конкретного подкласса, а тип переменной оставить базовым — Shape s = new Circle(1.5);.
Ошибка 2. Подкласс реализовал не все абстрактные методы.
abstract class Shape {
public abstract double area();
public abstract double perimeter();
}
class Square extends Shape {
private final double a;
Square(double a) { this.a = a; }
@Override
public double area() { return a * a; }
}
public class Main {
public static void main(String[] args) {
System.out.println(new Square(2).area());
}
}
Результат:
Main.java:6: error: Square is not abstract and does not override abstract method perimeter() in Shape
class Square extends Shape {
^
1 error
Исправление: добавить в Square метод public double perimeter() { return 4 * a; }. Если реализация пока не нужна, Square можно объявить abstract, но тогда и его объект создать не получится.
Ошибка 3. Недопустимые модификаторы. Метод, объявленный как private abstract void draw();, static abstract void make(); или final abstract void resize();, javac отвергает с сообщением illegal combination of modifiers: abstract and private (соответственно static, final). Исправление — убрать лишний модификатор; для закрытого вспомогательного кода нужен обычный private-метод с телом.
Текст сообщений приведен по выводу javac из OpenJDK 25 с английской локалью; в другой локали или другой сборке JDK формулировка может отличаться.
Конструктор абстрактного класса
Конструктор у абстрактного класса есть, даже если вы его не написали (тогда компилятор добавит конструктор без параметров). Вызывается он из конструктора подкласса — явно через super(...) или неявно первой строкой. Модификатор обычно делают protected: снаружи объект все равно не создать, а подклассам в других пакетах он доступен.
Подводный камень — вызов абстрактного (или любого переопределяемого) метода из конструктора базового класса:
abstract class Base {
Base() {
System.out.println("Base: конструктор");
init();
}
abstract void init();
}
class Child extends Base {
private String greeting = "привет";
Child() {
super();
System.out.println("Child: конструктор, greeting = " + greeting);
}
@Override
void init() {
System.out.println("Child.init: greeting = " + greeting);
}
}
public class Main {
public static void main(String[] args) {
new Child();
}
}
Результат:
Base: конструктор
Child.init: greeting = null
Child: конструктор, greeting = привет
Метод init() подкласса выполняется раньше, чем инициализированы поля Child, поэтому видит null. Для int было бы 0, для boolean — false. Компилятор это не ловит.
Исправление: передавать данные в базовый класс через параметры конструктора и не вызывать из конструктора методы, которые подкласс может переопределить.
abstract class Base {
private final String greeting;
protected Base(String greeting) {
this.greeting = greeting;
System.out.println("Base: greeting = " + greeting);
}
protected final String greeting() { return greeting; }
}
class Child extends Base {
Child() {
super("привет");
System.out.println("Child: " + greeting());
}
}
public class Main {
public static void main(String[] args) {
new Child();
Base anon = new Base("анонимный подкласс") { };
System.out.println(anon.getClass().getSuperclass().getSimpleName());
}
}
Результат:
Base: greeting = привет
Child: привет
Base: greeting = анонимный подкласс
Base
Последние две строки показывают границу правила «объект абстрактного класса не создать»: new Base(...) { } создает объект анонимного подкласса, его родитель — Base.
Абстрактный класс или интерфейс
До Java 8 выбор был простым: интерфейс — только сигнатуры, абстрактный класс — сигнатуры плюс код. С появлением default-методов интерфейс тоже умеет содержать реализацию, поэтому различие теперь в другом.
| Критерий | Абстрактный класс | Интерфейс |
|---|---|---|
| Состояние объекта (поля экземпляра) | Да | Нет, поля только public static final |
| Конструктор | Да | Нет |
| Реализованные методы | Любые | default, static, private (с Java 9) |
| Модификаторы абстрактных методов | public, protected, пакетный |
Только public |
| Сколько можно унаследовать | Один класс через extends |
Много через implements |
| Ограничить круг наследников | sealed ... permits (Java 17+) |
sealed ... permits (Java 17+) |
Можно ли применять к record |
Нет, record не наследует классы | Да, record реализует интерфейсы |
Практическое правило выбора, а не абсолютный закон:
- нужен контракт, который могут выполнять не связанные между собой классы, — интерфейс;
- у наследников есть общее состояние и общий алгоритм, а отличаются отдельные шаги — абстрактный класс;
- нужно и то и другое — интерфейс как тип плюс абстрактный класс как частичная реализация. Так устроена стандартная библиотека:
List— интерфейс,AbstractList— абстрактный класс, от которого удобно наследовать свою коллекцию.
Default-метод не заменяет абстрактный класс полностью: он не видит полей объекта (их у интерфейса нет), поэтому состояние может получать только через методы интерфейса, которые реализует класс. Если класс получает одинаковый default-метод из двух интерфейсов, компилятор потребует переопределить его явно.
Шаблонный метод и sealed: современный вариант
Самое частое применение абстрактного класса — паттерн «шаблонный метод»: базовый класс фиксирует порядок шагов в final-методе, а подклассы заполняют отдельные шаги. С Java 17 можно еще и ограничить список наследников ключевым словом sealed, а с Java 21 — проверять их в switch без ветки default.
sealed interface Payment permits Card, Cash {
default String currency() { return "RUB"; }
}
record Card(String last4, long amount) implements Payment { }
record Cash(long amount) implements Payment { }
abstract sealed class Report permits DailyReport, MonthlyReport {
private final String title;
protected Report(String title) { this.title = title; }
protected abstract String body();
public final String render() {
return "== " + title + " ==\n" + body();
}
}
final class DailyReport extends Report {
DailyReport() { super("День"); }
@Override protected String body() { return "платежей: 2"; }
}
final class MonthlyReport extends Report {
MonthlyReport() { super("Месяц"); }
@Override protected String body() { return "платежей: 41"; }
}
public class Main {
static String label(Payment p) {
return switch (p) {
case Card c -> "карта *" + c.last4() + ", " + c.amount() + " " + c.currency();
case Cash c -> "наличные, " + c.amount() + " " + c.currency();
};
}
static String kind(Report r) {
return switch (r) {
case DailyReport d -> "ежедневный";
case MonthlyReport m -> "ежемесячный";
};
}
public static void main(String[] args) {
System.out.println(label(new Card("4242", 1500)));
System.out.println(label(new Cash(300)));
Report r = new DailyReport();
System.out.println(kind(r));
System.out.println(r.render());
}
}
Результат:
карта *4242, 1500 RUB
наличные, 300 RUB
ежедневный
== День ==
платежей: 2
Здесь видно разделение ролей. Payment — sealed-интерфейс: его реализуют record-классы, у которых нет общего состояния. Report — sealed абстрактный класс: у наследников общее поле title и общий алгоритм render(), а отличается только body(). Каждый разрешенный наследник sealed-класса должен быть final, sealed или non-sealed.
Если не получилось
is abstract; cannot be instantiated— вы пишетеnewдля абстрактного класса. Создайте объект подкласса.is not abstract and does not override abstract method ...— в подклассе не хватает метода из списка в сообщении. Проверьте имя, параметры и тип возврата;@Overrideнад методом помогает поймать опечатку.missing method body, or declare abstract— у метода нет тела, но нет иabstract. Добавьте тело или модификатор (и тогда пометьте класс абстрактным).- Поле подкласса внезапно
nullили0— базовый конструктор вызывает переопределяемый метод. Передавайте данные черезsuper(...). the switch expression does not cover all possible input valuesдля sealed-типа — вswitchне хватаетcaseдля одного из наследников. Проверьте, что все наследники перечислены вpermitsи вcase, а версия языка не ниже 21.
Выводы
- Абстрактный класс в Java объявляется через
abstract, его объект нельзя создать черезnew, но можно использовать как тип переменной и родителя. - Абстрактный метод — сигнатура без тела; подкласс обязан его реализовать или сам стать абстрактным. Сочетать
abstractсprivate,staticиfinalу метода нельзя. - Конструктор у абстрактного класса есть и вызывается через
super(...); вызывать из него переопределяемые методы опасно — поля подкласса еще не инициализированы. - Интерфейс с default-методами описывает контракт без состояния; абстрактный класс нужен, когда у наследников общие поля и общий алгоритм.
- С Java 17 и абстрактный класс, и интерфейс можно сделать
sealed, чтобы компилятор знал полный список наследников и проверялswitch.
Где применяется / связь с практикой
Абстрактные классы встречаются в стандартной библиотеке (InputStream, AbstractList), во фреймворках и в собственном коде при проектировании иерархий: базовые обработчики, отчеты, шаги бизнес-процесса. На собеседовании Java-разработчика вопрос «чем абстрактный класс отличается от интерфейса» звучит почти всегда, и ответ «в интерфейсе нет реализации» с Java 8 считается устаревшим.
Освойте тему на практике
Системно пройти ООП в Java с практикой и ревью кода можно на курсе «Java разработчик. Базовый уровень». Посмотреть формат занятий бесплатно можно на открытых уроках Otus.
FAQ
Может ли абстрактный класс реализовать интерфейс и не реализовать его методы?
Да. Абстрактный класс может оставить методы интерфейса без реализации, их обязан будет реализовать первый конкретный подкласс.
Можно ли объявить абстрактный класс с методом main и запустить его?
Да. Статический main не требует объекта класса, поэтому java Main сработает, даже если Main абстрактный.
Можно ли сделать абстрактный метод protected?
Да. В абстрактном классе абстрактный метод может быть public, protected или пакетным; нельзя только private. При переопределении доступ можно расширить, но не сузить.



