Абстрактный класс в Java это класс, объявленный со словом
abstract. Создать его объект через
new нельзя: он служит заготовкой для классов-наследников. В нем можно объявить абстрактные методы, то есть методы без тела, и тогда каждый наследник обязан их реализовать или сам стать абстрактным. Обычные методы с готовой реализацией в абстрактном классе тоже разрешены.
Кратко
abstract перед классом означает, что это чертеж: собственных объектов у такого класса быть не может.- Абстрактный метод это метод без тела. Класс, в котором есть хотя бы один такой метод, обязан быть объявлен абстрактным.
- В абстрактном классе можно смешивать: часть методов реализовать, часть оставить абстрактными.
- Наследник обязан реализовать все абстрактные методы родителя либо сам объявиться абстрактным.
- Наследоваться можно только от одного класса, абстрактного или обычного: множественного наследования классов в Java нет.
- Знакомый пример из JDK:
java.util.Calendar абстрактный, а его наследник GregorianCalendar уже нет.
Привет! В прошлых лекциях мы
познакомились с интерфейсами и разобрались, для чего они нужны. Сегодняшняя тема будет перекликаться с предыдущей. Поговорим об
абстрактных классах в Java.
![Чертежи на столе с линейкой и лекалом: абстрактный класс это чертеж для будущих классов]()
Почему классы называют «абстрактными»
Ты наверняка помнишь, что такое «абстракция», мы это уже проходили :) Если вдруг подзабыл, не страшно, вспомним:
это принцип ООП, согласно которому при проектировании классов и создании объектов необходимо выделять только главные свойства сущности и отбрасывать второстепенные.
Например, если будем проектировать класс
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();
}
}
Эта «фишка» была реализована создателями 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, каждый класс-наследник должен его реализовать или быть объявленным как абстрактный. Иначе компилятор выбросит ошибку.
Разумеется, каждый класс может наследоваться только от одного абстрактного класса, так что в плане наследования разницы между абстрактными и обычными классами нет. Неважно, наследуемся мы от абстрактного класса или от обычного, класс-родитель может быть только один.
Абстрактный класс и обычный класс: в чем разница
Соберем все отличия, о которых мы говорили, в одну таблицу:
| Свойство | Обычный класс | Абстрактный класс |
|---|
Создать объект через new | можно | нельзя |
Методы без тела (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(), так что если мы не угадали, отключить устройство будет нельзя.
![Тостер, из которого поднимается ядерный гриб: наследник Toster и NuclearBomb не знает, какой метод on() вызвать]()
Именно из-за такой «непонятки», когда объекту неясно, какое поведение он должен выбрать, создатели Java отказались от множественного наследования. Впрочем, ты помнишь, что классы Java могут реализовывать сколько угодно интерфейсов.
Абстрактный класс из JDK: Calendar
Кстати, ты уже встречался в учебе как минимум с одним абстрактным классом!
Хотя, может, и не заметил этого :)
public abstract class Calendar implements Serializable, Cloneable, Comparable<Calendar>
Это твой старый знакомый, класс
Calendar. Он абстрактный, и у него есть несколько наследников. Один из них это
GregorianCalendar. Ты уже пользовался им в уроках о датах :)
Вроде бы все понятно, остался только один момент: в чем все-таки принципиальная
разница между абстрактными классами и интерфейсами? Зачем в Java добавили и то, и другое, а не ограничились чем-то одним? Этого ведь вполне могло хватить.
Об этом поговорим в следующей лекции! До встречи:)
Вопросы и ответы
Можно ли создать объект абстрактного класса?
Нет. Строка new Car() для абстрактного класса Car вызовет ошибку компиляции. Объект создают у класса-наследника, который реализовал все абстрактные методы.
Может ли абстрактный класс содержать обычные методы с реализацией?
Да. Часть методов можно реализовать прямо в абстрактном классе, тогда наследники получат это поведение по умолчанию. Остальные методы оставляют абстрактными и требуют реализации от наследника.
Может ли абстрактный класс не иметь ни одного абстрактного метода?
Да, компилятор это разрешает. Такой класс все равно нельзя создать через new, он остается заготовкой для наследников.
Обязан ли наследник реализовать все абстрактные методы родителя?
Да, либо реализовать их все, либо самому быть объявленным абстрактным и передать эту обязанность дальше по цепочке наследования. Иначе компилятор выбросит ошибку.
Может ли у абстрактного класса быть конструктор?
Да. Напрямую через new его не вызвать, но он выполняется при создании объекта наследника: конструктор наследника обращается к нему через super(). Так абстрактный класс инициализирует свои поля.
Почему в Java нельзя наследоваться сразу от двух классов?
Потому что у обоих родителей может оказаться метод с одинаковым именем, и объект не сможет определить, какой из них вызывать. В примере статьи и у тостера, и у ядерной бомбы есть метод on(), и наследник обоих не знал бы, готовить тост или взрываться.
Источник:
Oracle Java Tutorials: Abstract Methods and Classes.
Читайте также