Привет! В прошлых лекциях мы
познакомились с интерфейсами и разобрались, для чего они нужны. Сегодняшняя тема будет перекликаться с предыдущей. Поговорим об
абстрактных классах в Java.
![Абстрактные классы в Java на конкретных примерах - 1]()
Почему классы называют «абстрактными»
Ты наверняка помнишь, что такое «абстракция» — мы это уже проходили :) Если вдруг подзабыл — не страшно, вспомним:
это принцип ООП, согласно которому при проектировании классов и создании объектов необходимо выделять только главные свойства сущности, и отбрасывать второстепенные.
Например, если будем проектировать класс
SchoolTeacher
— школьный учитель — вряд ли понадобится характеристика «
рост». Действительно: для преподавателя эта характеристика не важна. Но вот если мы будем создавать в программе класс
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;
}
}
Вот так выглядит самый простой абстрактный класс. Как видишь, ничего особенного :)
Для чего он может нам понадобиться?
Прежде всего, он максимально абстрактно описывает нужную нам сущность — автомобиль. Слово
abstract находится здесь недаром. В мире не существует «просто машин». Есть грузовики, гоночные автомобили, седаны, купе, внедорожники.
Наш абстрактный класс — это просто «чертеж», по которому мы позже будем создавать классы-автомобили.
public class Sedan extends Car {
@Override
public void gas() {
System.out.println("Седан газует!");
}
@Override
public void brake() {
System.out.println("Седан тормозит!");
}
}
Это во многом похоже на то, о чем мы говорили в лекциях про наследование. Только там у нас класс
Car
и его методы не были абстрактными. Но у такого решения есть целый ряд минусов, которые в абстрактных классах исправлены.
Первое и главное —
экземпляр абстрактного класса создать нельзя:
public class Main {
public static void main(String[] args) {
Car car = new Car(); // Ошибка! Класс Car является абстрактным!
}
}
Эта «фишка» была реализована создателями Java специально. Еще раз, для запоминания:
абстрактный класс — это просто чертеж для будущих «нормальных» классов. Тебе же не нужны экземпляры чертежа, правильно? Вот и экземпляры абстрактного класса создавать не надо :)
А если бы класс
Car
не был абстрактным, мы легко могли бы создавать его объекты:
public class Car {
private String model;
private String color;
private int maxSpeed;
public void gas() {
// какая-то логика
}
public void brake() {
// какая-то логика
}
}
public class Main {
public static void main(String[] args) {
Car car = new Car(); // Все ок, машина создалась
}
}
Теперь у нас в программе появилась какая-то непонятная машина — не грузовик, не гоночная, не седан, а вообще непонятно что. Та самая «просто машина», которых в природе не существует.
Тот же пример можно привести с животными. Представь, если бы в твоей программе появились объекты
Animal
— «
просто животное». Какого оно вида, к какому семейству относится, какие у него характеристики — непонятно. Было бы странно увидеть его в программе. Никаких «просто животных» в природе не существует. Только собаки, кошки, лисы, кроты и другие.
Абстрактные классы избавляют нас от «
просто объектов». Они дают нам базовое состояние и поведение. Например, у всех машин должна быть
модель,
цвет и
максимальная скорость, а еще они должны уметь
газовать и
тормозить. Вот и все. Это — общая абстрактная схема, дальше ты уже сам проектируешь нужные тебе классы.
Обрати внимание: два метода в абстрактном классе тоже обозначены как
abstract, и они вообще не реализованы. Причина та же: абстрактные классы не создают «поведения по умолчанию» для «просто машин». Они просто говорят, что должны уметь делать все машины.
Впрочем, если поведение по умолчанию тебе все-таки нужно, методы в абстрактном классе можно реализовать. Java этого не запрещает:
public abstract class Car {
private String model;
private String color;
private int maxSpeed;
public void gas() {
System.out.println("Газуем!");
}
public abstract void brake();
//геттеры и сеттеры
}
public class Sedan extends Car {
@Override
public void brake() {
System.out.println("Седан тормозит!");
}
}
public class Main {
public static void main(String[] args) {
Sedan sedan = new Sedan();
sedan.gas();
}
}
Вывод в консоль:
“Газуем!”
Как видишь, мы реализовали в абстрактном классе один метод, а второй не стали.
В итоге поведение нашего класса
Sedan
разделилось на две части: если вызвать у него метод
gas()
, он «подтянется» из родительского абстрактного класса
Car
, а метод
brake()
мы переопределили в классе
Sedan
. Получилось очень удобно и гибко.
Но теперь
наш класс не такой уж и абстрактный? Ведь у него, по факту, половина методов реализована.
На самом деле — и это очень важная особенность —
класс является абстрактным, если хотя бы один из его методов является абстрактным. Хоть один из двух, хоть один из тысячи методов — без разницы.
Мы можем даже реализовать все методы и не оставить ни одного абстрактного. Будет абстрактный класс без абстрактных методов. В принципе, это возможно, и компилятор не выдаст ошибок, но лучше так не делать: слово abstract потеряет смысл, а твои коллеги-программисты сильно удивятся, увидев такое :/
При этом,
если метод помечен словом abstract, каждый класс-наследник должен его реализовать или быть объявленным как абстрактный. Иначе компилятор выбросит ошибку.
Разумеется, каждый класс может наследоваться только от одного абстрактного класса, так что в плане наследования разницы между абстрактными и обычными классами нет. Неважно, наследуемся мы от абстрактного класса или от обычного, класс-родитель может быть только один.
Почему в Java нет множественного наследования классов
Мы уже говорили, что в Java нет множественного наследования, но так толком и не разобрались почему. Давай попробуем сделать это сейчас.
Дело в том, что если бы в Java было множественное наследование, дочерние классы не могли бы определиться, какое именно поведение им выбрать.
Допустим, у нас есть два класса —
Toster
и
NuclearBomb
:
public class Toster {
public void on() {
System.out.println("Тостер включен, тост готовится!");
}
public void off() {
System.out.println("Тостер выключен!");
}
}
public class NuclearBomb {
public void on() {
System.out.println("Взрыв!");
}
}
Как видишь, у обоих есть метод
on()
. В случае с тостером он запускает приготовление тоста, а в случае с ядерной бомбой — устраивает взрыв.
Ой :/
А теперь представь, что ты решил (уж не знаю, с чего вдруг!) создать что-то среднее между ними. И вот он твой класс —
MysteriousDevice
!
Этот код, разумеется, нерабочий, и мы приводим его просто в качестве примера «а как оно могло бы быть»:
public class MysteriousDevice extends Toster, NuclearBomb {
public static void main(String[] args) {
MysteriousDevice mysteriousDevice = new MysteriousDevice();
mysteriousDevice.on(); // И что же здесь должно произойти? Мы получим тост, или ядерный апокалипсис?
}
}
Давай посмотрим, что у нас получилось. Загадочное устройство происходит одновременно и от Тостера, и от Ядерной Бомбы. У обоих есть метод
on()
, и в результате непонятно, какой из методов
on()
должен срабатывать у объекта
MysteriousDevice
, если мы его вызовем. Объект никак не сможет этого понять.
Ну и в качестве вишенки на торте: у Ядерной Бомбы нет метода
off()
, так что если мы не угадали, отключить устройство будет нельзя.
![Абстрактные классы в Java на конкретных примерах - 2]()
Именно из-за такой «непонятки», когда объекту неясно, какое поведение он должен выбрать, создатели Java отказались от множественного наследования. Впрочем, ты помнишь, что классы Java реализуют множество интерфейсов.
Кстати, ты уже встречался в учебе как минимум с одним абстрактным классом!
Хотя, может, и не заметил этого :)
public abstract class Calendar implements Serializable, Cloneable, Comparable<Calendar>
Это твой старый знакомый — класс
Calendar
. Он абстрактный, и у него есть несколько наследников. Одним из них —
GregorianCalendar
. Ты уже пользовался им в уроках о датах :)
Вроде бы все понятно, остался только один момент: в чем все-таки принципиальная
разница между абстрактными классами и интерфейсами? Зачем в Java добавили и то, и другое, а не ограничились чем-то одним? Этого ведь вполне могло хватить.
Об этом поговорим в следующей лекции! До встречи:)
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ