В каких случаях стоит использовать абстрактный класс, а в каких — интерфейс? Давайте разбираться, в чем между ними разница. Абстрактные классы и интерфейсы встречаются повсюду как в Java-приложениях, так и в самом Java Development Kit (JDK). Каждый из них служит своей цели: Интерфейс — это контракт, который должен быть реализован конкретным классом. Абстрактный класс похож на обычный, но отличается тем, что может содержать абстрактные методы — методы без реализации, и нельзя создать экземпляр абстрактного класса. Многие разработчики не видят разницы между интерфейсами и абстрактными классами, но на самом деле между ними есть весьма существенное различие. Интерфейс — это контракт, который реализуется в некотором классе. У интерфейса не может быть состояния, поэтому в нем нельзя использовать изменяемые поля экземпляра. В интерфейсе могут быть только неизменяемые final-поля. Интерфейсы очень полезны для уменьшения связанности (coupling) кода и реализации полиморфизма. Для примера давайте взглянем на интерфейс List из JDK: Как вы, вероятно, заметили, код весьма краток и лаконичен. Здесь мы видим сигнатуры методов, которые будут реализованы в конкретном классе, реализующем этот интерфейс. Контракт интерфейса List реализуется классами ArrayList, Vector, LinkedList и другими. При использовании полиморфизма тип переменной объявляем как List, и присваиваем ей любую из доступных реализаций. Например: Результат: В этом случае в каждом классе присутствует своя реализация методов. И это отличный пример использования интерфейсов. Если вы заметили, что ряд ваших классов содержит одинаковые методы, но с разными реализациями, то стоит использовать интерфейс. Помните, что интерфейс — это контракт, который должен быть реализован конкретным классом. Методы интерфейса неявно абстрактны и обязаны быть реализованы в классе, реализующем этот интерфейс. Рассмотрим следующий пример: Результат будет следующий: Обратите внимание еще раз, что методы интерфейса неявно абстрактны и их не нужно явно объявлять как abstract. Еще одно правило, которое следует помнить, заключается в том, что интерфейс может содержать только неизменяемые переменные. Следующий код вполне рабочий: Обратите внимание, что обе переменные неявно final и static. Это означает, что они являются константами, не зависят от экземпляра и не могут быть изменены. При попытке изменить поля в интерфейсе Challenger, например, следующим образом: будет ошибка компиляции: После появления в Java 8 методов по умолчанию, некоторые разработчики решили, что интерфейсы стали абстрактными классами. Однако это не так, поскольку у интерфейсов не может быть состояния. У методов по умолчанию может быть реализация, а у абстрактных методов — нет. Методы по умолчанию — результат появления лямбда-выражений и Stream API, но использовать их нужно с осторожностью. В качестве примера default-метода из JDK можно привести метод forEach() из интерфейса Iterable. Вместо копирования кода этого метода во все реализации Iterable, мы можем переиспользовать метод forEach: Любая реализация Iterable может использовать метод forEach() без необходимости реализации этого нового метода. Давайте рассмотрим пример с методом по умолчанию: Результат: Важно отметить, что у default-метода должна быть реализация и default-метод не может быть статическим. У абстрактных классов может быть состояние в виде изменяемых полей экземпляра. Например: Результат: Аналогично интерфейсам в абстрактных классах могут быть абстрактные методы. Абстрактный метод — это метод без тела (без реализации). Но в отличие от интерфейсов, абстрактные методы в абстрактных классах должны быть явно объявлены как абстрактные.  Попытка объявить метод без реализации и без ключевого слова 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). Могут быть любые методы. Давайте изучим основные различия между интерфейсами и абстрактными классами с помощью небольшой задачки. Вы также можете посмотреть данный материал в формате видео (англ.). В приведенном ниже коде объявлены интерфейс, абстрактный класс и используются лямбда-выражения. Как вы думаете, какой будет вывод, когда мы запустим этот код? Выберите один из следующих вариантов: Вариант 4 Эта задачка демонстрирует понятия об интерфейсах, абстрактных методах и о некоторых других вещах. Давайте разберем код строка за строкой. В первой строке main() присутствует лямбда-выражение для интерфейса Zombie. Обратите внимание, что в этой лямбде мы инкрементируем статическое поле. Здесь также можно было использовать поле экземпляра, но не локальную переменную, объявленную вне лямбда-выражения. То есть код компилируется без ошибок. Также обратите внимание, что это лямбда-выражение еще не выполняется, оно только объявлено, и поле nemesisRaids не будет увеличено. Далее мы выводим значение поля nemesisRaids, которое еще не увеличено. Следовательно, вывод будет: Еще один интересный момент заключается в том, что мы используем анонимный внутренний класс. Мы создаем не экземпляр абстрактного класса Nemesis, но экземпляр анонимного класса, расширяющего Nemesis. Также обратите внимание, что первый конкретный класс в иерархии наследования всегда будет обязан реализовать абстрактные методы. В интерфейсе Zombie есть поле с типом интерфейса Zombie, объявленное с помощью лямбда-выражения. Поэтому, когда мы вызываем метод Zombie.zombie.shoot(), получим следующий вывод: В следующей строке вызывается лямбда-выражение, которое мы создали в начале. Следовательно, переменная nemesisRaids будет увеличена. Однако, поскольку мы используем оператор постинкремента, она будет увеличена только после этого выражения. Следующий вывод будет: Далее вызовем метод shoot для nemesis, который изменяет поле экземпляра shoots на 23. Обратите внимание, что как раз здесь мы видим основную разницу между интерфейсом и абстрактным классом. Наконец, мы выводим значение nemesis.shoots и nemesisRaids. Правильный ответ — вариант 3: Материал подготовлен в преддверии старта специализации Java-разработчик.  Недавно в рамках специализации прошел открытый урок, на котором мы обсудили алгоритм бинарного поиска, разобрались, почему он быстрее линейного. А также познакомились с понятием «О-большое». Делимся записью этого урока.