Абстрактные классы и интерфейсы в Java
Абстрактные классы и интерфейсы встречаются повсюду как в Java-приложениях, так и в самом Java Development Kit (JDK). Каждый из них служит своей цели:
- Интерфейс — это контракт, который должен быть реализован конкретным классом.
- Абстрактный класс похож на обычный, но отличается тем, что может содержать абстрактные методы — методы без реализации, и нельзя создать экземпляр абстрактного класса.
Многие разработчики не видят разницы между интерфейсами и абстрактными классами, но на самом деле между ними есть весьма существенное различие.
Интерфейсы
Интерфейс — это контракт, который реализуется в некотором классе. У интерфейса не может быть состояния, поэтому в нем нельзя использовать изменяемые поля экземпляра. В интерфейсе могут быть только неизменяемые final-поля.
Когда использовать интерфейсы
Интерфейсы очень полезны для уменьшения связанности (coupling) кода и реализации полиморфизма. Для примера давайте взглянем на интерфейс List из JDK:
public interface List extends Collection
Как вы, вероятно, заметили, код весьма краток и лаконичен. Здесь мы видим сигнатуры методов, которые будут реализованы в конкретном классе, реализующем этот интерфейс.
Контракт интерфейса List реализуется классами ArrayList , Vector , LinkedList и другими.
При использовании полиморфизма тип переменной объявляем как List , и присваиваем ей любую из доступных реализаций. Например:
List list = new ArrayList(); System.out.println(list.getClass()); List list = new LinkedList(); System.out.println(list.getClass());
class java.util.ArrayList class java.util.LinkedList
В этом случае в каждом классе присутствует своя реализация методов. И это отличный пример использования интерфейсов. Если вы заметили, что ряд ваших классов содержит одинаковые методы, но с разными реализациями, то стоит использовать интерфейс.
Переопределение метода интерфейса
Помните, что интерфейс — это контракт, который должен быть реализован конкретным классом. Методы интерфейса неявно абстрактны и обязаны быть реализованы в классе, реализующем этот интерфейс.
Рассмотрим следующий пример:
public class OverridingDemo < public static void main(String[] args) < Challenger challenger = new JavaChallenger(); challenger.doChallenge(); >> interface Challenger < void doChallenge(); >class JavaChallenger implements Challenger < @Override public void doChallenge() < System.out.println("Challenge done!"); >>
Результат будет следующий:
Challenge done!
Обратите внимание еще раз, что методы интерфейса неявно абстрактны и их не нужно явно объявлять как abstract.
Неизменяемые переменные
Еще одно правило, которое следует помнить, заключается в том, что интерфейс может содержать только неизменяемые переменные. Следующий код вполне рабочий:
public interface Challenger
Обратите внимание, что обе переменные неявно final и static . Это означает, что они являются константами, не зависят от экземпляра и не могут быть изменены.
При попытке изменить поля в интерфейсе Challenger , например, следующим образом:
Challenger.number = 8; Challenger.name = "Another Challenger";
будет ошибка компиляции:
Cannot assign a value to final variable 'number' Cannot assign a value to final variable 'name'
Default-методы
После появления в Java 8 методов по умолчанию, некоторые разработчики решили, что интерфейсы стали абстрактными классами. Однако это не так, поскольку у интерфейсов не может быть состояния.
У методов по умолчанию может быть реализация, а у абстрактных методов — нет. Методы по умолчанию — результат появления лямбда-выражений и Stream API, но использовать их нужно с осторожностью.
В качестве примера default-метода из JDK можно привести метод forEach() из интерфейса Iterable . Вместо копирования кода этого метода во все реализации Iterable , мы можем переиспользовать метод forEach :
default void forEach(Consumer action) < // Code implementation here…
Любая реализация Iterable может использовать метод forEach() без необходимости реализации этого нового метода.
Давайте рассмотрим пример с методом по умолчанию:
public class DefaultMethodExample < public static void main(String[] args) < Challenger challenger = new JavaChallenger(); challenger.doChallenge(); >> class JavaChallenger implements Challenger < >interface Challenger < default void doChallenge() < System.out.println("Challenger doing a challenge!"); >>
Challenger doing a challenge!
Важно отметить, что у default-метода должна быть реализация и default-метод не может быть статическим.
Абстрактные классы
У абстрактных классов может быть состояние в виде изменяемых полей экземпляра. Например:
public abstract class AbstractClassMutation < private String name = "challenger"; public static void main(String[] args) < AbstractClassMutation abstractClassMutation = new AbstractClassImpl(); abstractClassMutation.name = "mutated challenger"; System.out.println(abstractClassMutation.name); >> class AbstractClassImpl extends AbstractClassMutation
mutated challenger
Абстрактные методы в абстрактных классах
Аналогично интерфейсам в абстрактных классах могут быть абстрактные методы. Абстрактный метод — это метод без тела (без реализации). Но в отличие от интерфейсов, абстрактные методы в абстрактных классах должны быть явно объявлены как абстрактные.
public abstract class AbstractMethods
Попытка объявить метод без реализации и без ключевого слова abstract , например, следующим образом:
public abstract class AbstractMethods
приведет к ошибке компиляции:
Missing method body, or declare abstract
Когда использовать абстрактные классы
Рекомендуется использовать абстрактный класс, когда вам нужно изменяемое состояние. В качестве примера можно привести класс AbstractList из Java Collections Framework, который использует состояние.
Если хранить состояние класса не нужно, обычно лучше использовать интерфейс.
Хороший пример использования абстрактных классов — паттерн "шаблонный метод" (template method). Шаблонный метод манипулирует переменными экземпляра (полями) внутри конкретных методов.
Различия между абстрактными классами и интерфейсами
С точки зрения объектно-ориентированного программирования основное различие между интерфейсом и абстрактным классом заключается в том, что интерфейс не может иметь состояния, тогда как абстрактный класс может (в виде полей экземпляра).
Другое ключевое различие заключается в том, что классы могут реализовывать более одного интерфейса, но расширять только один абстрактный класс. Множественное наследование может привести к тупиковым ситуациям в коде, поэтому авторы Java решили этого избежать, отказавшись от него.
Еще одно различие состоит в том, что интерфейс может быть реализован классом или расширен другим интерфейсом, а класс может быть только расширен.
Также важно отметить, что лямбда-выражения могут использоваться только с функциональными интерфейсами (интерфейс только с одним методом), но не с абстрактными классами с одним абстрактным методом.
В таблице 1 обобщены различия между абстрактными классами и интерфейсами.
Таблица 1. Сравнение интерфейсов и абстрактных классов
Интерфейсы
Абстрактные классы
Могут содержать только final static поля. Интерфейс никогда не может изменять свое состояние.
Могут быть любые поля, в том числе статические, изменяемые и неизменяемые.
Класс может реализовывать несколько интерфейсов.
Класс может расширять только один абстрактный класс.
Может быть реализован с помощью ключевого слова implements.
Может расширять другой интерфейс с помощью extends.
Может быть только расширен с помощью extends.
Можно использовать только static final поля. Параметры и локальные переменные в методах.
Могут быть изменяемые поля экземпляра. Параметры и локальные переменные в методах.
В лямбда-выражениях могут использоваться только функциональные интерфейсы.
Абстрактные классы с одним абстрактным методом не могут использоваться в лямбда-выражениях.
Не может быть конструктора.
Может содержать конструктор.
Могут быть абстрактные методы.
Могут быть default и static методы (c Java 8).
Могут быть private методы с реализацией (с Java 9).
Могут быть любые методы.
Задачка
Давайте изучим основные различия между интерфейсами и абстрактными классами с помощью небольшой задачки. Вы также можете посмотреть данный материал в формате видео (англ.).
В приведенном ниже коде объявлены интерфейс, абстрактный класс и используются лямбда-выражения.
public class AbstractResidentEvilInterfaceChallenge < static int nemesisRaids = 0; public static void main(String[] args) < Zombie zombie = () ->System.out.println("Graw. " + nemesisRaids++); System.out.println("Nemesis raids: " + nemesisRaids); Nemesis nemesis = new Nemesis() < public void shoot() < shoots = 23; >>; Zombie.zombie.shoot(); zombie.shoot(); nemesis.shoot(); System.out.println("Nemesis shoots: " + nemesis.shoots + " and raids: " + nemesisRaids); > > interface Zombie < Zombie zombie = () ->System.out.println("Stars. "); void shoot(); > abstract class Nemesis implements Zombie
Как вы думаете, какой будет вывод, когда мы запустим этот код? Выберите один из следующих вариантов:
Вариант 1
Compilation error at line 4
Вариант 2
Graw. 0 Nemesis raids: 23 Stars. Nemesis shoots: 23 and raids:1
Вариант 3
Nemesis raids: 0 Stars. Graw. 0 Nemesis shoots: 23 and raids: 1
Nemesis raids: 0 Stars. Graw. 1 Nemesis shoots: 23 and raids:1
Вариант 5
Compilation error at line 6
Разбор задачи
Эта задачка демонстрирует понятия об интерфейсах, абстрактных методах и о некоторых других вещах. Давайте разберем код строка за строкой.
В первой строке main() присутствует лямбда-выражение для интерфейса Zombie. Обратите внимание, что в этой лямбде мы инкрементируем статическое поле. Здесь также можно было использовать поле экземпляра, но не локальную переменную, объявленную вне лямбда-выражения. То есть код компилируется без ошибок. Также обратите внимание, что это лямбда-выражение еще не выполняется, оно только объявлено, и поле nemesisRaids не будет увеличено.
Далее мы выводим значение поля nemesisRaids , которое еще не увеличено. Следовательно, вывод будет:
Nemesis raids: 0
Еще один интересный момент заключается в том, что мы используем анонимный внутренний класс. Мы создаем не экземпляр абстрактного класса Nemesis , но экземпляр анонимного класса, расширяющего Nemesis . Также обратите внимание, что первый конкретный класс в иерархии наследования всегда будет обязан реализовать абстрактные методы.
В интерфейсе Zombie есть поле с типом интерфейса Zombie , объявленное с помощью лямбда-выражения. Поэтому, когда мы вызываем метод Zombie.zombie.shoot() , получим следующий вывод:
Stars.
В следующей строке вызывается лямбда-выражение, которое мы создали в начале. Следовательно, переменная nemesisRaids будет увеличена. Однако, поскольку мы используем оператор постинкремента, она будет увеличена только после этого выражения. Следующий вывод будет:
Graw. 0
Далее вызовем метод shoot для nemesis , который изменяет поле экземпляра shoots на 23. Обратите внимание, что как раз здесь мы видим основную разницу между интерфейсом и абстрактным классом.
Наконец, мы выводим значение nemesis.shoots и nemesisRaids .
Nemesis shoots: 23 and raids: 1
Правильный ответ — вариант 3:
Nemesis raids: 0 Stars. Graw. 0 Nemesis shoots: 23 and raids: 1
Материал подготовлен в преддверии старта специализации Java-разработчик.
Недавно в рамках специализации прошел открытый урок, на котором мы обсудили алгоритм бинарного поиска, разобрались, почему он быстрее линейного. А также познакомились с понятием «О-большое». Делимся записью этого урока.
- java
- абстрактные классы
- интерфейсы
- бинарный поиск
- Блог компании OTUS
- Программирование
- Java
Чем отличается абстрактный класс от интерфейса

Абстрактные классы vs интерфейсы в Java
Различия между абстрактными классами и интерфейсами в Java
В процессе профессионального развития каждого начинающего программиста всегда наступает тот момент, когда требуется понять и осознать разницу между интерфейсом и абстрактным классом. Ясно представлять себе границы их использования, а также уметь выбирать, что из этого лучше применить в том или ином месте кода.
Кроме того, обсуждаемый в данной статье вопрос о различиях, является одним из самых распространенных на собеседованиях, на который зачастую отвечают либо неполно, либо неверно.
Почему же на этот вопрос отвечают неправильно? Дело в том, что Java развивается, и границы отличий между абстрактным классом и интерфейсом постепенно стираются. А новички, читая устаревшие книги по Java (или не читая их вовсе) или даже современные статьи-перепечатки, где рассказывается об интерфейсах до Java 8, не имеют представления о том, что в своем развитии они ушли далеко вперед.
Например, до появления Java 8, при возникновении вопроса на собеседовании типа: «Назовите разницу между абстрактным классом и интерфейсом», вы смело могли сказать, что интерфейс представляет собой контракт, содержащий методы без реализации, которые должны быть реализованы в классах, имплементирующих этот интерфейс. А абстрактный класс — это класс, который содержит (или не содержит) абстрактные методы и общий для его потомков код.
Также раньше вы могли сказать, что интерфейс не может содержать статические, приватные или методы по умолчанию, и что в нем вообще не может быть реализовано никакой логики. В техническом плане все ограничения остались в прошлом — граница между этими понятиями сейчас уже почти стерта. Но с фундаментальной точки зрения ничего не изменилось!
В данной статье будут рассмотрены как фундаментальные, так и технические различия между интерфейсом и абстрактным классом.
1. Фундаментальное отличие
Фундаментальная разница между интерфейсом и абстрактным классом заключается в том, что интерфейс определяет только поведение. Он не сообщает ничего про объект, который будет его реализовывать.
Например, такое поведение как «движение» может быть применимо к разным типам объектов: машина, кот, котировки курса и т. д. Эти объекты не имеют ничего общего, кроме того, что они могут двигаться.
Перефразируем немного иначе — если существует «движущийся» объект, то глядя на реализованный им интерфейс, мы не сможем понять, какой именно тип объекта имеется ввиду. Это может быть машина, кот, котировка курса валют и много других вариантов.
Если перенести это поведение в код, написанный на языке Java, то получится следующее:
interface Movable
Абстрактный же класс описывает некий абстрактный объект (автомобиль, человека, кота и т. д.), а не только поведение.
Если мы рассуждаем про абстрактный «автомобиль», то в нашей голове сразу формируется картинка с объектом. Мы сразу понимаем, что автомобиль содержит двигатель, колеса и может двигаться, поворачивать, ускоряться или тормозить. Он также будет иметь поля для хранения внутренних деталей, таких как мотор, колеса и тормоза. Вы получите все это просто сказав слово «автомобиль».
При этом кот и котировка курса валют не могут быть автомобилем, даже если они также могут двигаться.
На основе вышесказанного создадим класс Automobile:
abstract class Automobile
Интерфейс и абстрактный класс — понятия не взаимозаменяемые. Даже если абстрактный класс с абстрактными методами выглядит подобно интерфейсу, а интерфейс, с его методами по умолчанию, подобен абстрактному классу с методами, имеющими реализации, то это все равно будут два фундаментально разных понятия. Если нам нужно поведение — необходимо использовать интерфейс. Если речь про концептуальный объект — мы должны использовать абстрактный класс.
А теперь поговорим про различия в технической реализации.
2. Технические отличия
В этом разделе мы обсудим технические (синтаксические) различия в реализации этих концепций в Java. Именно так обычно отвечают на вопрос о разнице между интерфейсами и абстрактными классами (но мы-то уже знаем, что это только вторая часть ответа, и при этом не главная).
2.1. Синтаксис создания
Итак, при создании абстрактного класса указывается ключевое слово abstract, а при определении интерфейса — interface.
Пример абстрактного класса:
public abstract class MyAbstractClass < // поля и конструкторы // абстрактные методы // методы с реализацией >
Пример интерфейса:
public interface MyInterface < // объявление констант // методы без реализации // статические методы // методы по умолчанию (default) // приватные методы >
2.2 Синтаксис использования
При наследовании от абстрактного класса используется ключевое слово extends (с англ. «расширяет»), а при реализации интерфейса — implements (с англ. «реализует»).
В приведенном ниже коде класс MyClass расширяет MyAbstractClass:
public class MyClass extends MyAbstractClass < // реализация абстрактных методов // иной код >
А тут класс MyClass реализует интерфейс MyInterface:
public class MyClass implements MyInterface < // реализация методов интерфейса // иной код >
Класс может одновременно и наследоваться от абстрактного класса (только одного) и реализовать один или множество интерфейсов.
Например, класс MyClass реализует интерфейс MyInterface, MyInterface_2 и MyInterface_3:
class MyClass extends MyAbstractClass implements MyInterface, MyInterface_2, MyInterface_3 < // реализация абстрактных методов абстрактного класса // реализация методов из интерфейсов // иной код >
2.3 Наличие конструктора
Как вы знаете, невозможно создать экземпляр абстрактного класса. Но, объявить и определить в нем конструктор мы можем. В противном случае за нас это сделает компилятор, создав конструктор по умолчанию. Без него код просто не скомпилируется, поскольку при создании конкретного класса первым оператором будет неявный вызов super() конструктора суперкласса, в данном случае абстрактного.
Добавим в абстрактный класс MyAbstractClass конструктор:
public abstract class MyAbstractClass < public MyAbstractClass() < System.out.println("Конструктор из MyAbstractClass"); >>
Также добавим конструктор в MyClass:
public class MyClass extends MyAbstractClass < public MyClass() < System.out.println("Конструктор из MyClass"); >>
В классе Main создадим объект и запустим программу:
public class Main < public static void main(String[] args) < MyAbstractClass myObject = new MyClass(); >>
Результат выполнения программы:

Для интерфейсов понятия «конструктор» не существует.
2.4 Типы переменных
Все переменные в интерфейсах неявно являются public static final (т.е. константами). «final» подразумевает, что переменной обязательно должно быть присвоено значение во время инициализации.
Рассмотрим следующий код:
public interface MyInterface < // эта строка не скомпилируется int value_1; int value_2 = 1; public final int value_3 = 1; static int value_4 = 1; public final static int value_5 = 1; static final int value_6 = 1; >
Поскольку value _1 не присвоено конкретное значение, а она является неявно final, код с такой строкой не скомпилируется. Остальные строки не вызовут ошибок, т.к. public static final можно не указывать (если указать, то IDE выделит их серым цветом, подчеркивая «избыточность»).
В абстрактных классах переменные могут быть любыми — абстрактность класса не накладывает ограничений.
Например, следующий код является корректным:
public abstract class MyAbstractClass
2.5 Модификаторы доступа методов
Модификаторы доступа для абстрактных классов могут быть любыми. При этом, все методы, кроме абстрактных, должны иметь реализацию.
А вот в случае с интерфейсами модификаторы доступа могут быть только двух типов, public и private (последний — начиная с Java 9).
Причем private может быть применим только к методам, имеющим реализацию, которые, в свою очередь, могут использоваться только методами по умолчанию, находящимися в интерфейсе. Класс, реализующий интерфейс, не будет иметь к ним доступ. Методы интерфейса без реализации являются неявно public, поэтому этот модификатор можно не писать.
Рассмотрим следующий код:
public interface MyInterface_2 < void publicAbstractMethod_1(); public void publicAbstractMethod_2(); void publicAbstractMethod_3(); private void privateMethod() < // Реализация метода >>
Методы publicAbstractMethod_1 и publicAbstractMethod_3 являются неявно public.
2.6 Методы с реализацией
Для абстрактного класса все методы, кроме абстрактных, должны иметь реализацию.
Для интерфейсов, начиная с Java 8, вводится понятие метода по умолчанию. Такие методы, во-первых, должны иметь реализацию в интерфейсе, а во-вторых, помечены ключевым словом default. Они также являются неявно public. При этом они не должны в обязательном порядке иметь реализацию в реализующем интерфейс классе, но могут быть в нем переопределены.
Также, начиная с Java 8, в интерфейсах допустимы статические методы, которые неявно являются public, но могут быть явно private. Т. е. если указать модификатор доступа private, метод будет приватным, а если ничего не указывать, то публичным.
Например, код ниже скомпилируется:
public interface MyInterface_3 < default void defaultMethod() < // Реализация метода >public default void defaultMethod_2() < // Реализация метода >private static void privateStaticMethod() < // Реализация метода >public static void publicStaticMethod_1() < // Реализация метода >static void publicStaticMethod_2() < // Реализация метода >>
2.7 Наследование
Интерфейс не может реализовывать интерфейс, не может наследовать абстрактный класс, но может наследовать (используя ключевое слово extends) множество других интерфейсов.
Абстрактный класс может наследовать как обычный класс, так и абстрактный. В обоих случаях это будет только один класс (в Java нет множественного наследования классов).
В то же время абстрактный класс также может реализовать до 65 535 интерфейсов (это связано с ограничением константы interfaces_count в структуре ClassFile).
В этом месте давайте немного отойдем от темы сравнения и затронем множественное наследование. В некоторых источниках однозначно указывается, что множественного наследования в Java нет. В других источниках говорится, что множественного наследования как бы нет, но оно реализуется через интерфейсы.
- наследование состояния (Inheritance of State)
- наследование реализации (Inheritance of Implementation)
- наследование типа (Inheritance of Type)
Java не поддерживает множественное наследование состояния, но поддерживает множественное наследование реализации (как вариант, на основе методов по умолчанию от интерфейсов). Она также поддерживает множественное наследование типов (поскольку может реализовать более одного интерфейса).
Именно таким должен быть ответ на вопрос о множественном наследовании в Java.
3. Рекомендации к применению абстрактных классов и интерфейсов
В соответствии с рекомендациями Oracle абстрактный класс нужно использовать в следующих случаях:
- Необходимо выделить общий код между несколькими тесно связанными классами
Пояснение: это типовой рефакторинг, целью которого является устранение дублирования кода.
- Мы ожидаем, что классы, расширяющие абстрактный класс, имеют много общих методов, полей или требуют модификаторов доступа, отличных от public (protected и private)
Пояснение: ранее мы писали, что в интерфейсах методы, имеющие реализацию (помеченные ключевым словом default), являются неявно public. Если же метод, имеющий реализацию, помечен явно как private, то он не сможет быть использован в классах, реализующих этот интерфейс, а только в других методах интерфейса. Поэтому, если нам нужны методы с модификаторами доступа не public, мы должны использовать абстрактный класс.
- Мы хотим объявить не static или не final поля для изменения состояния объекта.
Пояснение: ранее мы писали, что все переменные в интерфейсах неявно являются public static final — из-за чего они не могут быть изменены.
Для использования интерфейсов существуют следующие причины:
- Планируется, что несвязанные между собой классы будут реализовывать интерфейс. Например, интерфейсы Comparable и Cloneable реализуются многими несвязанными между собой классами.
Пояснение: как мы выяснили ранее, концептуальное назначение интерфейса — описание «поведения», а не «состояния» (в отличие от абстрактного класса). Соответственно, реализовать это поведение могут любые классы, несвязанные между собой, которые просто должны что-то «уметь» делать или «как-то» себя вести.
- Требуется детализировать или определить поведение определенного типа данных, но при этом мы не хотим беспокоиться о том, кто реализует его поведение.
Пояснение: пояснение к прошлому пункту также актуально и для этого — нам все равно, какие классы будут реализовывать наш интерфейс и в каких пакетах находятся. Мы просто хотим, чтобы они вели себя в соответствии с «нашим контрактом», прописанным в интерфейсе в виде методов без реализации.
- Мы хотим воспользоваться преимуществами множественного наследования типов
Приведем наглядный пример выбора между абстрактным классом и интерфейсом.
Существует паттерн «Шаблонный метод», определение которого звучит так: «шаблонный метод определяет основу алгоритма и позволяет подклассам переопределить некоторые его шаги, не изменяя структуру, в целом».
Суть паттерна заключается в размещении в абстрактном классе метода с реализацией, в котором вызываются абстрактные методы. Далее подклассы абстрактного класса реализуют их каждый по-своему.
Так вот вопрос, а что нам мешает разместить абстрактные и шаблонные методы в интерфейсе? В качестве шаблонного метода мы могли бы использовать метод по умолчанию. Вроде звучит логично, согласитесь?
Но обратите еще раз внимание на определение шаблонного метода. Там есть такие слова: «… не изменяя структуру, в целом». Подклассы абстрактного класса не должны иметь возможность переопределить шаблонный метод с целью изменения логики (алгоритма) его работы. По этой причине шаблонные методы объявляются как final и не могут использовать интерфейсы с их методами по умолчанию (могут быть переопределены) вместо абстрактных классов. Вся идея паттерна в этом случае будет нарушена.
Для того чтобы рассмотреть разницу между интерфейсом и абстрактным классом немного глубже, рекомендуется обратиться к книге «Java. Эффективное программирование», Блох Д., 3-е издание.
В разделе 4.6 этой книги говорится о следующих причинах предпочтения интерфейсов абстрактным классам:
1. Существующие классы можно легко приспособить для реализации нового интерфейса.
Пояснение: для этого достаточно написать implements и реализовать необходимые методы. Но уже имеющиеся классы в общем случае не могут быть переделаны для расширения нового абстрактного класса. Если вы хотите, чтобы два класса расширяли один и тот же абстрактный класс, вам придется поднять этот класс в иерархии настолько высоко, чтобы он стал предком обоих этих классов. Это может привести к нарушению логики иерархии типов, заставляя всех потомков нового абстрактного класса расширять его независимо от того, насколько это целесообразно.
2. Интерфейсы идеально подходят для создания миксинов.
Пояснение: Миксин (mixin) — это тип, который класс может реализовать в дополнение к своему «первичному типу», объявляя о том, что этот класс предоставляет некоторое необязательное поведение. Например, Comparable является таким интерфейсом-миксином, т. к. добавляет (примешивает) к первоначальным возможностям типа дополнительную функциональность.
Использовать абстрактные классы для создания миксинов нельзя по той же причине, по которой их невозможно приспособить к уже имеющимся классам: класс не может иметь больше одного родителя, и в иерархии классов нет подходящего места, куда можно поместить миксин.
3. Интерфейсы позволяют создавать неиерархические каркасы типов.
Пояснение: иерархии типов прекрасно подходят для организации некоторых сущностей, но зато сущности других типов невозможно аккуратно уложить в строгую иерархию. Альтернативой им является раздутая иерархия классов.
4. Интерфейсы обеспечивают безопасное и мощное развитие функциональности с использованием шаблона «Декоратор». Паттерн «Декоратор» позволяет динамически (в ходе выполнения программы) добавлять объекту новые возможности (состояние и/или поведение) на основе композиции.
Абстрактные классы в Java и их отличия от интерфейсов: кратко и без воды
Смотрим на примерах, что такое абстрактный класс, когда лучше использовать интерфейсы и что такое абстрактные методы.


Иллюстрация: Dana Moskvina / Skillbox Media

Паша Лебедев
Пишет на Java. Верит в Agile. Пьёт много кофе и не любит грязный код.
Абстрактный класс — это класс, который содержит методы без реализации. Но зачем он такой нужен? Давайте разбираться.
Представим, что нам нужно описать несколько животных — и у каждого будет свой класс. Мы соберём их характеристики, опишем в полях и методах. У некоторых животных характеристики будут совпадать — например, и у гепарда, и у коня 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 — то есть класс-наследник только расширяет функциональность абстрактного класса.
- Проект уже частично написан, а вы знаете, что выбранные классы будут часто меняться и дополняться новыми методами и полями. То есть поддерживать абстрактный класс и дополнять его намного проще, чем проектировать интерфейс и добавлять его новые методы во все места в коде, где они должны реализовываться.
Когда лучше использовать интерфейсы:
- Вам нужно описать определённую логику, которую должны поддерживать не связанные между собой объекты.
- Вам нужно привести к одному типу группу объектов и гарантировать схожую функциональность.
- Вам необходимо добавить какой-то маркер, который будет говорить о том, что выбранные классы поддерживают определённую логику.
- Вы хотите использовать множественное наследование типа.
Это лишь базовые и самые распространённые ситуации. Решая конкретные задачи, лучше советоваться с более опытными разработчиками или принимать самостоятельное решение.
Заключение
В этой статье мы с вами познакомились с понятием абстрактного класса, разобрались, для чего используются абстрактные классы, научились реализовывать их и проанализировали, в каких ситуациях лучше использовать интерфейсы, а в каких — абстрактные классы.
Читайте также:
- Что такое класс в Java
- Как разработчик на C++ превратил свой пет-проект в прибыльный стартап
- Основные принципы ООП: большой гайд по объектно-ориентированному программированию
Абстрактный класс vs. интерфейс
— Привет, Амиго! Билаабо расскажет тебе о различиях абстрактного класса и интерфейса. Их несколько.
| Абстрактный класс | Интерфейс |
|---|---|
| Наследование | |
| Абстрактный класс может унаследоваться только от одного класса и любого количества интерфейсов . | Интерфейс не может наследоваться от классов , но может от любого количества интерфейсов . |
| Абстрактные методы | |
| Абстрактный класс может содержать абстрактные методы . Но может и не содержать их вообще. | Все не статические и не default методы интерфейса – абстрактные – не содержат реализации. Интерфейс может не содержать никаких методов вообще. |
| Методы с реализацией | |
| Абстрактный класс может содержать методы с реализацией . | Интерфейс может содержать методы по умолчанию (default methods). |
| Данные | |
| Никаких ограничений. | Интерфейс содержит только public final static данные. |
| Создание объекта | |
| Нельзя создать объект абстрактного класса. | Нельзя создать объект интерфейса. |
— Вот это я понимаю. Кратко и по существу.

Комментарии (299)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Anonymous #2277416 Уровень 13
1 января 2024
Интерфейс содержит только public final static данные. Все переменные в интерфейсах неявно являются public static final (т.е. константами). Методы в интерфейсах могут быть private, если они имеют реализацию. Источник (рекомендую): Различия между абстрактными классами и интерфейсами в Java
JavaPure Уровень 23
12 декабря 2023
А где более продвинутый паскаль?
Максим Li Уровень 36
12 ноября 2023
Edf Уровень 28
8 ноября 2023
все вроде ясно, один момент не понятен - кто такой Билаабо? Что такая короткая лекция? Он на пол-ставки штоле? И куда делся первый кореш Диего?
M10beretta Уровень 27
7 октября 2023
Различия между абстрактным классом и интерфейсом (Java 9+) часть 1 из 2-x 1 Ключевое слово создания abstract | interface 2 Расширение extends может расширить 1 класс | может расширить 65535 интерфейсов (в том числе абстрактный) расширяется классом | расширяется интерфейсом (в том числе абстрактным) 3 Реализация implements может реализовать | не реализует 65535 интерфейсов не реализуется | реализуется классом (в том числе абстрактным) класс (в том числе абстрактный) может реализовать 65535 интерфейсов 4 Видимость полей и методов по умолчанию в пределах пакета | без ограничений 5 Поля без ограничений | public static final (все) 6 Блоки инициализации без ограничений | запрещен 7 Конструктор без ограничений | запрещен