Абстрактные классы в Java и их отличия от интерфейсов: кратко и без воды
Смотрим на примерах, что такое абстрактный класс, когда лучше использовать интерфейсы и что такое абстрактные методы.


Иллюстрация: Dana Moskvina / Skillbox Media
Абстрактный класс — это класс, который содержит методы без реализации. Но зачем он такой нужен? Давайте разбираться.
Представим, что нам нужно описать несколько животных — и у каждого будет свой класс. Мы соберём их характеристики, опишем в полях и методах. У некоторых животных характеристики будут совпадать — например, и у гепарда, и у коня 4 ноги. А у некоторых — не совпадут: кролик ест траву, а тигр — других животных.
Однако, несмотря на несовпадение, мы можем выделить шаблонные характеристики: количество лап, издаваемый звук, приём пищи, передвижение. Эти шаблонные характеристики мы можем собрать в одном месте — абстрактном классе «Животное» и указать компилятору, что наполним их конкретными данными и поведением позже — в классах отдельных животных или групп животных.
Так на практике реализуется абстракция — процесс выделения наиболее важных характеристик объекта и информации об объекте. То есть мы обобщаем основную информацию о свойствах разных предметов или объектов и оставляем только самое важное, а менее важным пренебрегаем.
При этом абстрактным объект класса «Животное» создать нельзя — ведь в природе «просто животного» не существует, есть только волки, лисы, бегемоты и прочие слоны.
Простыми словами о том, что такое абстракция, рассказали в одной из статей большого руководства по объектно-ориентированному программированию. Там вы найдете множество примеров использования абстрактных классов и интерфейсов.
Давайте рассмотрим пример абстрактного класса в Java.
public abstract class Animal {
Integer countOfLegs;
Double weight;
String color;
public abstract void run();
public void eat() {
System.out.println("Animal says nyam-nyam");
}
}
Как вы видите, абстрактный класс очень похож на обычный класс. Чтобы сделать класс абстрактным, нам нужно добавить ключевое слово abstract в описание класса и в методы, которые мы хотим оставить абстрактными, — то есть определить уже в конкретных классах (медведь, лиса, лягушка).
В абстрактном классе мы можем описать некоторые поля, которые будут отражать определённые свойства объекта или добавить обычный метод с реализацией по умолчанию.
Именно так мы и сделали в примере выше: добавили переменные для веса, цвета и количества ног у животного, а также описали два метода: run («бежать») и eat («кушать»). В нашем случае метод run — абстрактный, а у метода eat есть реализация по умолчанию. Это значит, что в классах, которые наследуют от Animal, мы обязаны добавить определение метода run, а переопределять или нет метод eat, зависит от наших задач и желания.
Теперь давайте попробуем создать класс на основе абстрактного класса. Пусть это будет класс, описывающий собаку.
public class Dog extends Animal {
@Override
public void run() {
System.out.println("Dog says gaf-gaf");
}
}
Здесь extends Animal означает, что мы наследуем класс Animal. Как только мы создадим пустой класс Dog, компилятор сразу подсветит его красным и выведет такое сообщение:
Error: (3, 8) java: com.company.Dog is not abstract and does not override abstract method run () in com.company.Animal
Это значит, что мы обязаны переопределить метод run() — что мы и сделали с помощью аннотации @Override.
Теперь давайте создадим объект класса Dog. Как мы видим, после его создания нам доступны все переменные и методы из класса Animal. Попробуем вызвать eat и run.
public static void main(String[] args) {
Dog scooby = new Dog();
scooby.eat();
scooby.run();
}
Вот что мы увидим в консоли:
Animal says nyam-nyam
Dog says gaf-gaf
Итак, давайте выделим важные моменты:
- Мы не можем создавать объекты абстрактных классов — но в этом и нет смысла.
- В абстрактный класс можно добавлять методы с реализацией по умолчанию.
- Если мы наследуемся от абстрактного класса, то мы обязаны переопределить все абстрактные методы родительского класса (кроме тех, которые имеют реализацию по умолчанию) или сделать весь дочерний класс абстрактным.
- Абстрактные методы не имеют тела.
Больше рецептов на Java — в нашей базе знаний.
Отличия абстрактного класса и интерфейса
На собеседованиях часто задают вопрос, чем отличаются абстрактные классы и интерфейсы. Вот их основные отличия:
- Интерфейсы описывают только часть функциональности объекта — определённые признаки. Абстрактный класс же может описывать целую категорию разных объектов, а его характеристики имеют право наследовать только те объекты, которые являются частью этой категории. Например, собаки и волки — часть общей категории «Животные», а интерфейс, описывающий умение бегать, может реализовать и человек, и робот, и собака.
- Интерфейс описывает только поведение (методы), и у него нет полей. Точнее, есть возможность их объявить, но они будут public static final. В то же время абстрактный класс может содержать классические поля, которые будут принадлежать разным объектам.
- Наследник абстрактного класса обязан наследовать все его составляющие, а интерфейс создан только для реализации (имплементирования). Поэтому в Java мы можем наследовать класс только от одного класса, а на реализацию интерфейсов внутри одного класса ограничений нет.
Когда использовать абстрактный класс, а когда — интерфейс
Интерфейсы и абстрактные классы — это разные инструменты, разработанные для разных целей, хотя у них и есть общие черты.
Когда лучше использовать абстрактные классы:
- Вы хотите избежать дублирования кода, реализуя несколько тесно связанных классов из одной семантической категории.
- Классы, которые будут расширять ваш абстрактный класс, имеют много общих свойств и будут реализовывать много похожих методов.
- Наследуемый класс используется в отношении IS-A — то есть класс-наследник только расширяет функциональность абстрактного класса.
- Проект уже частично написан, а вы знаете, что выбранные классы будут часто меняться и дополняться новыми методами и полями. То есть поддерживать абстрактный класс и дополнять его намного проще, чем проектировать интерфейс и добавлять его новые методы во все места в коде, где они должны реализовываться.
Когда лучше использовать интерфейсы:
- Вам нужно описать определённую логику, которую должны поддерживать не связанные между собой объекты.
- Вам нужно привести к одному типу группу объектов и гарантировать схожую функциональность.
- Вам необходимо добавить какой-то маркер, который будет говорить о том, что выбранные классы поддерживают определённую логику.
- Вы хотите использовать множественное наследование типа.
Это лишь базовые и самые распространённые ситуации. Решая конкретные задачи, лучше советоваться с более опытными разработчиками или принимать самостоятельное решение.
Заключение
В этой статье мы с вами познакомились с понятием абстрактного класса, разобрались, для чего используются абстрактные классы, научились реализовывать их и проанализировали, в каких ситуациях лучше использовать интерфейсы, а в каких — абстрактные классы.