Абстрактные классы и интерфейсы очень популярны во всех объектно-ориентированных языках программирования. И практически на каждом собеседовании по Java попадается хотя бы один вопрос на эту тему. Интерфейсы упоминаются чаще, из-за их популярности среди проектировщиков ПО, но время от времени встречаются и вопросы по абстрактным классам. Последние чаще задают претендентам на должность джуниор-разработчиков, скажем, с не более чем двухлетним опытом разработки на Java, в то время как вопросы по интерфейсам чаще всего встречаются при собеседовании тех, чей опыт уже перевалил за четыре года. В основном, их задают вместе с другими вопросами по паттернам проектирования Java, например, по паттернам Decorator (Декоратор) или Factory (Фабрика). В этой статье мы рассмотрим частые вопросы по абстрактным классам и интерфейсам, которые задавались на собеседованиях по Java разного уровня. Большинство из них не должно представлять сложности даже для начинающего Java-программиста. В основном это вопросы на чистое знание, но некоторые из них, например, о различиях между абстрактными классами и интерфейсами в Java или о том, когда лучше предпочесть абстрактный класс интерфейсу, могут оказаться достаточно непростыми. Предлагаем вам десяток интересных вопросов по теме.
Если вам когда-либо задавали на собеседовании или приходилось задавать какой-либо стоящий внимания вопрос об абстрактных классах и интерфейсах, но его в данном списке нет, делитесь им в комментариях.

1. Могут ли в языке Java у абстрактного класса быть конструкторы?

Да, в абстрактном классе в Java можно объявить и определить конструкторы. Поскольку создавать экземпляры абстрактных классов нельзя, вызвать такой конструктор можно только при формировании цепочки конструкторов, то есть при создании экземпляра конкретного класса-реализации. Но представьте, что интервьюер задаст затем вопрос: а какой смысл в конструкторе, если создать экземпляр абстрактного класса все равно нельзя? Дело в том, что его всё равно можно использовать для задания начальных значений общих переменных, объявленных в абстрактном классе и используемых различными реализациями. Даже если вы не объявили никакого конструктора, компилятор добавит в абстрактный класс конструктор по умолчанию без аргументов. Без него ваш подкласс не скомпилируется, поскольку первый оператор в любом конструкторе представляет собой неявный вызов super() – конструктора суперкласса по умолчанию в языке Java.

2. Могут ли абстрактные классы в языке Java реализовывать интерфейсы? Должны ли они реализовывать все методы?

Да, абстрактные классы могут реализовывать интерфейсы с помощью ключевого слова implements. Поскольку они абстрактные, то не обязаны реализовывать все методы. Наличие абстрактного базового класса и интерфейса для объявления типа является рекомендуемой практикой. Пример — интерфейс java.util.List и соответствующий абстрактный класс java.util.AbstractList. Поскольку AbstractList реализует все общие методы, то конкретные реализации (например, LinkedList и ArrayList) не должны реализовать все методы, как в случае, если бы они реализовали интерфейс List напрямую. Это решение сочетает преимущество использования интерфейса для объявления типа и гибкость абстрактного класса для реализации всего общего поведения в одном месте. В книге Джошуа Блоха «Java. Эффективное программирование» есть отличная глава на тему использования интерфейсов и абстрактных классов в Java, для лучшего понимания имеет смысл её изучить.

3. Может ли абстрактный класс быть final?

Нет, не может. Ключевое слово final означает, что класс на вершине иерархии, и у него не может быть наследников. А абстрактный класс без наследников — это сферический конь в вакууме, так как нельзя создать экземпляр abstract class. Таким образом, если класс одновременно abstract и final, то у него нет наследников и нельзя создать его экземпляр. Компилятор Java выдаст ошибку, если сделать класс одновременно abstract и final.

4. Могут ли у абстрактного класса в языке Java быть статические методы?

Да, абстрактные классы могут объявлять и определять статические методы. Только необходимо следовать общим принципам создания статических методов в Java, поскольку они нежелательны при объектно-ориентированном проектировании, ведь переопределение статических методов в Java невозможно. Статические методы в абстрактном классе – явление очень редкое, но, если на это есть уважительные причины, вам ничего не помешает их использовать.

5. Можно ли создать экземпляр абстрактного класса?

Нет, этого делать нельзя. Суть абстрактного класса заключается в том, что он не завершён, и его нужно завершить в классах-наследниках. То есть этот класс не готов к использованию. В нём, например, может отсутствовать реализация каких-то методов. Раз класс не готов к использованию, то нельзя создавать его объект. А вот экземпляры наследников абстрактного класса создавать можно. Компилятор Java выдаст ошибку, если программа попытается создать экземпляр абстрактного класса.

6. Обязательно ли в абстрактном классе должны быть абстрактные методы?

Нет, в абстрактном классе может не быть ни одного абстрактного метода. Сделать класс абстрактным в языке Java можно просто путем использования ключевого слова abstract при объявлении. Компилятор обеспечит выполнение всех структурных ограничений, например, запрета на создание экземпляров этого класса. Кстати, вопрос о том, должны ли быть абстрактные методы в абстрактном классе или интерфейсе – спорный. Мне представляется, что в абстрактном классе должны быть абстрактные методы, поскольку это первое, о чем думает программист, видя абстрактный класс. Это хорошо согласуется с принципом минимизации неожиданностей.

7. Каковы различия между абстрактным классом и интерфейсом в Java?

Это важнейший и один из самых классических вопросов на собеседованиях по языку Java. Я не могу сосчитать, сколько раз встречал этот вопрос на собеседованиях по Java для всех уровней. Интересным этот вопрос делает, в частности, возможность для соискателя представить пример. Отвечать на вопросы по основам объектно-ориентированного программирования, например, рассказать об абстракции, инкапсуляции, полиморфизме и наследовании, легко, но, когда дело доходит до подобных тонких нюансов, претенденты на должность очень часто теряются и говорят, что первое приходит в голову. Ответ на этот вопрос тянет на отдельную статью (особенно после изменений в Java 8), тем не менее, если кратко:
  • Интерфейс описывает только поведение (методы) объекта, а вот состояний (полей) у него нет (кроме public static final), в то время как у абстрактного класса они могут быть.

  • Абстрактный класс наследуется (etxends), а интерфейс — реализуется (implements). Мы можем наследовать только один класс, а реализовать интерфейсов — сколько угодно. Интерфейс может наследовать (extends) другой интерфейс/интерфейсы.

  • Абстрактные классы используются, когда есть отношение "is-a", то есть класс-наследник расширяет базовый абстрактный класс, а интерфейсы могут быть реализованы разными классами, вовсе не связанными друг с другом.

8. Когда имеет смысл предпочесть абстрактный класс интерфейсу и наоборот?

Это продолжение предыдущих вопросов по абстрактным классам и интерфейсам. Если вы знаете, каковы их синтаксические различия, то ответ на этот вопрос не доставит вам проблем, так как именно они служат определяющим фактором принятия решения. Поскольку в опубликованный интерфейс практически невозможно добавить новый метод, в случае потенциальной необходимости доработки лучше использовать абстрактный класс. Развивать абстрактные классы в Java проще, чем интерфейсы. Аналогично, если в интерфейсе слишком много методов и реализация их всех становится настоящей головной болью, лучше создать абстрактный класс для реализации по умолчанию. Этому паттерну следуют и в пакете коллекций Java, абстрактный класс AbstractList обеспечивает реализацию по умолчанию для интерфейса List. Используйте абстрактные классы, если:
  • Вы хотите поделиться кодом между несколькими тесно связанными классами.

  • Вы ожидаете, что классы, которые расширяют ваш абстрактный класс, имеют много общих методов или полей, или требуют других модификаторов доступа, кроме public (например, protected и private).

  • Вы хотите объявить нестатические или не-final поля. Это позволяет вам определять методы, которые могут получить доступ и изменить состояние объекта, которому они принадлежат.
Используйте интерфейсы, если:
  • Вы ожидаете, что несвязанные классы будут реализовывать ваш интерфейс. Например, интерфейсы Comparable и Cloneable реализуются многими несвязанными классами.

  • Вы хотите определить поведение конкретного типа данных, но вам не важно, кто его реализует.

  • Вы хотите использовать множественное наследование типа.

9. Что такое абстрактный метод в языке Java?

Абстрактный метод – это метод без тела. Вы просто объявляете метод, не определяя его, с использованием ключевого слова abstract в объявлении метода. Все объявленные внутри интерфейса в языке Java методы – по умолчанию абстрактные. Вот пример абстрактного метода в языке Java:
public void abstract printVersion();
Теперь, для реализации этого метода необходимо расширить абстрактный класс и этот метод переопределить.

10. Может ли абстрактный класс в Java содержать метод main?

Да, абстрактный класс в Java может содержать метод main, ведь это просто еще один статический метод, и абстрактный класс можно выполнять при помощи метода main, если не создавать его экземпляров. Вот и всё, что я хотел рассказать. И помните: абстрактные классы и интерфейсы – ключевые проектные решения в процессе объектно-ориентированного анализа и проектирования, и применять их следует с должной осмотрительностью, конечно, если вы хотите создать гибкую систему.
Что еще почитать?

Абстрактные классы в Java на конкретных примерах

Сказ о двух итераторах: стратегии конкурентной модификации в Java

Машинный код и байт код: на каком языке говорит ваша программа?