Можно ли обойтись без интерфейсов в Java
Что проверяют
Понимаете ли вы, какую задачу решает интерфейс, и отличаете ли её от ритуала «на каждый класс свой интерфейс». Следом спрашивают про разницу с абстрактным классом или про то, как без интерфейса писать моки.
Как отвечать
В своём коде без интерфейсов обойтись можно, в Java целиком нельзя. Язык сам на них опирается:
- лямбды: работают только с функциональными интерфейсами,
Runnable,Function,Comparator; - синтаксис: цикл for-each требует
Iterable, try-with-resources требуетAutoCloseable; - коллекции: вы пишете
List<User>, а неArrayList<User>, чтобы не привязываться к реализации.
Интерфейс нужен, когда у контракта реально несколько реализаций (разные платёжные провайдеры, хранилище в памяти и в базе) или когда это граница модуля: порт в гексагональной архитектуре, публичный API библиотеки. Пара UserService и UserServiceImpl с единственной реализацией чаще просто лишний файл.
Современные аргументы, которые стоит знать:
- Mockito с пятой версии по умолчанию использует inline mock maker и мокает обычные и даже
finalклассы, так что для тестов интерфейс больше не обязателен; - Spring Boot по умолчанию строит прокси через CGLIB наследованием от класса, интерфейс для
@Transactionalне нужен. Ноfinalкласс или метод такой прокси не перехватит; - интерфейсы стали богаче:
defaultиstaticметоды с Java 8,privateс Java 9, аsealed interfaceвместе сrecordиswitchпо шаблонам дают закрытую иерархию.
Абстрактный класс выбирают, когда нужны общее состояние, конструктор или защищённые методы, помня, что наследоваться можно только от одного класса.
Где спотыкаются
Говорят «без интерфейсов нельзя тестировать». Это устарело: моки классов работают давно, а интерфейс ради одного теста только размывает код.
Уходят в крайность и заводят интерфейс на каждый сервис заранее, «вдруг понадобится». Выделить интерфейс, когда появится вторая реализация, дело пары минут в IDE.
Путают default методы с полноценным множественным наследованием. Состояния в интерфейсе нет, а при конфликте двух default методов с одинаковой сигнатурой компилятор заставит переопределить метод явно.