🔥 🚀 Важно для всех, кто работает с Java! 🔥
На JavaRocks ты найдешь уникальные туториалы, практические задачи и редкие книги, которых не найти в свободном доступе. Присоединяйся к нашему Telegram-каналу JavaRocks — стань частью профессионального сообщества!
Всем привет! Сегодня мы поговорим о том, что такое абстрактные классы, приведем конкретные примеры и проанализируем особенности наследования классов в Java.

Почему классы называются “абстрактными”
Абстракция – это принцип ООП, который заключается в том, что при проектировании классов и создании объектов мы должны определять только ключевые свойства сущности и отбрасывать второстепенные.
Например, если мы проектируем класс SchoolTeacher
, нам вряд ли понадобится свойство ‘height’ (рост), поскольку для учителя оно несущественно. При создании же класса BasketballPlayer
рост будет важной характеристикой.
Итак, абстрактный класс — это, по сути, “заготовка” для группы будущих классов. Саму заготовку нельзя использовать напрямую — она слишком “сырая”. Однако она описывает базовое состояние и общее поведение, которые будут унаследованы дочерними классами.
Примеры абстрактных классов Java
Рассмотрим простой пример с автомобилями:
public abstract class Car {
private String model;
private String color;
private int maxSpeed;
public abstract void gas();
public abstract void brake();
public String getModel() {
return model;
}
public void setModel(String model) {
this.model = model;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public int getMaxSpeed() {
return maxSpeed;
}
public void setMaxSpeed(int maxSpeed) {
this.maxSpeed = maxSpeed;
}
}
Run CodeВот как выглядит самый простой абстрактный класс. В приведенном примере он описывает сущность Car
(автомобиль) максимально абстрактно, потому что в реальном мире не существует “абстрактных” автомобилей: есть грузовики, гоночные машины, седаны, купе и внедорожники.
Наш абстрактный класс – это просто “чертёж”, который мы будем использовать в дальнейшем для создания классов автомобилей.
public class Sedan extends Car {
@Override
public void gas() {
System.out.println("The sedan is accelerating!");
}
@Override
public void brake() {
System.out.println("The sedan is slowing down!");
}
}
Run CodeВот пример конкретного класса Sedan
, который наследуется от абстрактного класса Car
.
Преимущество рассматриваемого метода состоит в том, что мы не можем создать копию абстрактного класса:
public class Main {
public static void main(String[] args) {
Car car = new Car(); // Error! The Car class is abstract!
}
}
Run CodeАбстрактный класс – “чертеж” для дальнейшего создания классов. А копии “чертежа” создавать бессмысленно, и создатели Java это предусмотрели.
Если бы класс Car
был не абстрактным, мы могли бы спокойно создавать его экземпляры:
public class Car {
private String model;
private String color;
private int maxSpeed;
public void gas() {
// Some logic
}
public void brake() {
// Some logic
}
}
public class Main {
public static void main(String[] args) {
Car car = new Car(); // Everything is fine. A car is created.
}
}
Run CodeТаким образом, мы создали совершенно непонятный “абстрактный” автомобиль, которого не существует в природе – это и не грузовик, и не гоночная машина, и не седан.
Абстрактные классы не позволяют создать абстрактные объекты. Они задают базовое состояние и поведение. Например, все автомобили должны иметь модель, цвет и максимальную скорость, а также уметь ускоряться и тормозить. Это общий абстрактный план. На его основе мы можем проектировать конкретные классы.
Обратите внимание! В созданном нами абстрактном классе Car
есть два абстрактных метода ( gas()
и brake()
), они не имеют реализации. Причина состоит в том, что абстрактные классы не задают по умолчанию поведение для абстрактного автомобиля, а лишь указывают, что должна уметь делать каждая машина.
При необходимости можно реализовать методы и в абстрактном классе:
public abstract class Car {
private String model;
private String color;
private int maxSpeed;
public void gas() {
System.out.println("Gas!");
}
public abstract void brake();
// Getters and setters
}
public class Sedan extends Car {
@Override
public void brake() {
System.out.println("The sedan is slowing down!");
}
}
public class Main {
public static void main(String[] args) {
Sedan sedan = new Sedan();
sedan.gas();
}
}
Run CodeКонсольный вывод:
“Gas!”
Таким образом, мы реализовали только первый метод, оставив второй нереализованным.
В результате поведение нашего класса Sedan
разделилось на две части: при вызове gas()
происходит обращение к родительскому абстрактному классу Car, а метод brake()
мы переопределили в классе Sedan
. Это довольно удобный и гибкий подход.
Если половина методов реализована, значит, наш класс не такой уж и абстрактный?
Вовсе нет. Класс является абстрактным, если хотя бы один из его методов является абстрактным.
Мы даже можем реализовать все методы и не оставить ни одного абстрактного. Тогда это будет абстрактный класс без абстрактных методов. В принципе, это возможно, и компилятор не будет выдавать ошибок, но лучше этого избегать: слово “абстрактный” теряет смысл, а ваши коллеги-программисты будут очень удивлены 🙂
В то же время, если метод помечен словом abstract, каждый дочерний класс должен реализовать его или объявить как абстрактный. В противном случае компилятор выдаст ошибку.
Конечно, каждый класс может наследовать только один абстрактный класс, поэтому с точки зрения наследования нет никакой разницы между абстрактными и обычными классами. Неважно, наследуем мы абстрактный класс или обычный, родительский класс может быть только один.
Почему в Java нет множественного наследования классов
Если бы в Java поддерживалось множественное наследование, дочерние классы не смогли бы решить, какое именно поведение им выбрать.
Предположим, у нас есть два класса – Toaster
и NuclearBomb
:
public class Toaster {
public void on() {
System.out.println("The toaster is on. Toast is being prepared!");
}
public void off() {
System.out.println("The toaster is off!");
}
}
public class NuclearBomb {
public void on() {
System.out.println("Boom!");
}
}
Run CodeВ обоих классах представлен метод on()
. Только для тостера он запускает процесс приготовления, а для ядерной бомбы инициирует взрыв.
Представим, что мы хотим создать нечто среднее – класс MysteriousDevice
.
Этот код не работает, но мы приводим его только в качестве примера:
public class MysteriousDevice extends Toaster, NuclearBomb {
public static void main(String[] args) {
MysteriousDevice mysteriousDevice = new MysteriousDevice();
mysteriousDevice.on(); // So what should happen here? Do we get toast or a nuclear apocalypse?
}
}
Run CodeMysteriousDevice
наследует Toaster
и NuclearBomb
. Если мы вызовем метод on()
, то будет непонятно, какая из его реализаций должна сработать.
Кроме того, у NuclearBomb
нет метода off()
, поэтому, если мы сделали неправильный выбор, “выключить” устройство будет невозможно. Из-за такой путаницы создатели Java и отказались от множественного наследования.
Тем не менее, классы могут реализовывать несколько интерфейсов. Например:
public abstract class Calendar implements Serializable, Cloneable, Comparable<Calendar>
Это абстрактный класс Calendar
, он имеет несколько дочерних классов, один из них – GregorianCalendar
.
Перевод статьи «Abstract classes in Java with specific examples».
Читайте также: «Отношения между классами: наследование, композиция и агрегация»