Абстрактные классы и методы в Java
Строка Figure figure = new Figure(10, 10); выглядит безобидно, но компилятор отвечает Figure is abstract; cannot be instantiated. Одно слово abstract в объявлении класса навсегда запрещает создавать его объекты — и это не ограничение, а главный смысл конструкции: класс описывает что умеют наследники, не говоря как.
Абстрактный класс в Java — это класс, объявленный с модификатором abstract, объект которого создать нельзя; он служит общим предком для подклассов. Абстрактный метод — это метод без реализации: у него есть сигнатура, но нет тела. Само слово abstract переводится как «абстрактный, отвлечённый» — отвлечённый от конкретной реализации.
Абстрактный метод: синтаксис и правила
Абстрактные методы — это методы, у которых отсутствует реализация. Вместо тела в фигурных скобках после сигнатуры сразу ставится точка с запятой.
Общая форма:
abstract тип имяМетода(списокПараметров); Пример:
public abstract double calculateArea(); Правила абстрактного метода:
- у него нет тела — только сигнатура и точка с запятой;
- он должен быть реализован в первом неабстрактном наследнике (промежуточный наследник может остаться абстрактным и передать обязанность дальше);
- объявить его можно только внутри абстрактного класса или интерфейса;
- он не может быть одновременно
static,finalилиprivate.
Абстрактный класс: объявление и объекты
Любой класс, содержащий хотя бы один абстрактный метод, обязан сам быть объявлен абстрактным. Для этого достаточно указать ключевое слово abstract перед словом class:
public abstract class Figure {
// ...
public abstract double calculateArea();
} У абстрактного класса не может быть собственных объектов, но его тип можно использовать для ссылок на объекты наследников:
public class Demo {
public static void main(String[] args) {
// Figure figure = new Figure(10, 10); // ошибка компиляции:
// Figure is abstract; cannot be instantiated
Figure figure = new Rectangle(10, 20); // так можно: ссылка типа Figure
System.out.println(figure.calculateArea());
}
} Именно ради этой возможности абстракция и нужна: переменная, параметр метода или элемент коллекции объявляются общим абстрактным типом, а конкретный объект подставляется во время выполнения. Это базовый приём абстракции в ООП — работать с сущностью через её обобщённый контракт, не зная реализации.
Важно
Обратное утверждение неверно: класс может быть абстрактным без единого абстрактного метода. Такой класс тоже нельзя инстанцировать — это законный способ сказать «пользуйтесь наследниками, а не мной». В JDK так сделаны, например, java.util.AbstractList и java.util.Calendar.
Пример: Figure, Rectangle и Triangle
Возьмём классы Figure, Triangle и Rectangle из урока «Переопределение методов». Там Figure был обычным классом и возвращал бессмысленный ноль площади. Класс описывает абстрактную фигуру, у которой площади не существует, поэтому calculateArea() правильнее сделать абстрактным, а сам класс — абстрактным.

public abstract class Figure {
double dimension1;
double dimension2;
public Figure(double dimension1, double dimension2) {
this.dimension1 = dimension1;
this.dimension2 = dimension2;
}
public abstract double calculateArea();
} Обратите внимание: у абстрактного класса есть поля и конструктор, хотя объектов у него не бывает. Конструктор нужен наследникам — они вызывают его через super(...), чтобы инициализировать унаследованную часть состояния.
Любой подкласс, производный от абстрактного класса, обязан реализовать все абстрактные методы суперкласса либо сам быть объявлен абстрактным:
public class Rectangle extends Figure {
public Rectangle(double dimension1, double dimension2) {
super(dimension1, dimension2);
}
@Override
public double calculateArea() {
System.out.println("В области четырёхугольника.");
return dimension1 * dimension2;
}
} public class Triangle extends Figure {
public Triangle(double dimension1, double dimension2) {
super(dimension1, dimension2);
}
@Override
public double calculateArea() {
System.out.println("В области треугольника.");
return dimension1 * dimension2 / 2;
}
} Совет
Ставьте @Override над реализацией абстрактного метода. Если вы ошибётесь в имени или списке параметров, компилятор сразу скажет об этом, а без аннотации получится новый метод, и класс останется абстрактным по факту — с невнятной ошибкой в другом месте файла.
Массив абстрактного типа и полиморфизм
Объектов у абстрактного класса нет, а вот массив (или List) абстрактного типа создать можно: он хранит ссылки на объекты наследников.
public class FindAreasExample {
public static void main(String[] args) {
Figure[] figures = new Figure[3];
// figures[0] = new Figure(10, 10); // не скомпилируется: Figure абстрактный
figures[0] = new Rectangle(10, 10);
figures[1] = new Rectangle(20, 10);
figures[2] = new Triangle(10, 10);
for (Figure figure : figures) {
double area = figure.calculateArea();
System.out.println(area);
}
}
} Результат выполнения программы:
В области четырёхугольника.
100.0
В области четырёхугольника.
200.0
В области треугольника.
50.0 Компилятор видит только тип Figure и разрешает вызов, потому что метод объявлен в абстрактном классе. Какая именно реализация выполнится, решает JVM во время работы программы по реальному типу объекта — это динамическое связывание.
Что можно и что нельзя в абстрактном классе
| Элемент | Разрешён? | Пояснение |
|---|---|---|
| Абстрактные методы | Да | Ноль, один или несколько — ограничений нет |
| Обычные методы с телом | Да | Готовое поведение, общее для всех наследников |
Поля, в том числе не-final | Да | Обычное состояние; в интерфейсе так нельзя |
| Конструктор | Да | Вызывается из наследника через super(...) |
| Статические методы и блоки | Да | Но статический метод не может быть абстрактным |
Создание объекта new | Нет | cannot be instantiated; исключение — анонимный подкласс |
abstract + final | Нет | Противоречие: final запрещает наследование, а без него абстрактный класс бесполезен |
abstract + static у метода | Нет | Статические методы не переопределяются, реализацию подставить некому |
abstract + private у метода | Нет | private не виден наследнику, реализовать его невозможно |
| Абстрактный конструктор | Нет | Конструкторы не наследуются и не переопределяются |
Абстрактный класс или интерфейс
После появления default-методов в Java 8 граница между интерфейсом и абстрактным классом стала тоньше, но не исчезла.
| Критерий | Абстрактный класс | Интерфейс |
|---|---|---|
| Сколько можно унаследовать | Только один | Сколько угодно |
| Изменяемое состояние (поля) | Да, любые поля | Нет, только public static final |
| Конструктор | Есть | Нет |
| Модификаторы доступа членов | Любые, включая protected и private | По умолчанию public |
| Когда выбирать | Наследники — вариации одной сущности с общим состоянием и кодом | Нужен контракт-роль для несвязанных классов |
На чём чаще всего ошибаются
- Пытаются создать объект абстрактного класса.
new Figure(10, 10)не компилируется, даже если у класса есть конструктор. - Забывают реализовать метод в наследнике. Тогда компилятор потребует либо реализацию, либо пометить сам наследник как
abstract. - Сужают видимость при реализации. Метод, объявленный
public abstract, нельзя реализовать какprotected— доступ разрешено только расширять. - Делают абстрактным класс без наследников. Абстракция без реализаций — это просто код, который нельзя выполнить.
- Путают «абстрактный» с «пустым». Метод с телом
{ }илиreturn 0;— это обычный метод: компилятор не заставит его переопределить, и ошибка всплывёт уже во время работы программы.
Часто задаваемые вопросы
Можно ли создать объект абстрактного класса?
Напрямую нельзя: new Figure(10, 10) даёт ошибку компиляции. Но можно создать объект анонимного подкласса — запись new Figure(10, 10) { public double calculateArea() { return 0; } } компилируется, потому что здесь создаётся не сам Figure, а безымянный наследник, реализующий абстрактный метод.
Зачем абстрактному классу конструктор, если объектов у него нет?
Конструктор абстрактного класса инициализирует унаследованные поля и вызывается из конструктора наследника через super(...). Если явного вызова нет, компилятор подставит super() сам — и если конструктора без параметров у абстрактного класса не окажется, наследник не скомпилируется.
Что будет, если наследник не реализует абстрактный метод?
Компилятор выдаст ошибку вида «is not abstract and does not override abstract method». Выхода два: реализовать метод или объявить сам наследник абстрактным и переложить обязанность на следующий класс в цепочке наследования.
Может ли абстрактный класс наследоваться от обычного?
Да. Абстрактный класс может расширять любой неабстрактный класс, добавляя к нему абстрактные методы. Обратное тоже верно: обычный класс наследуется от абстрактного, если реализует все его абстрактные методы.
Абстрактный класс или интерфейс: что выбрать?
Абстрактный класс берут, когда у наследников есть общее состояние и готовый код, а сами они — разновидности одной сущности. Интерфейс берут, когда нужно описать роль или способность для не связанных между собой классов, либо когда одному классу требуется несколько таких ролей: интерфейсов можно реализовать сколько угодно, а унаследовать класс — только один.
Видео объяснение
Предпочитаете видеоформат? Посмотрите этот урок с примерами и объяснениями.
Комментарии