Дефолтные методы

Нужно где-то положить конец этому вопросу. Его раньше любили на собеседованиях. Да и для программиста не лишним будет окончательное понимание как оно работает.

Итак, в Java8 появились дефолтные методы в интерфейсах. Появились как костыль, на котором удержалось backward compatibility. Например, как имплементировать следующий код не положив код написанный в ранних версиях Java?

Ведь если мы добавим метод в интерфейс List, то предыдущие версии попадают с ошибкой, поскольку такого метода нет в их имплементации наследников этого интерфейса. А если я скажу что метод stream() добавлен ещё выше? В интерфейсе Collection. А forEach() в Iterable? Ляжет ВСЁ! Можно было бы создать отдельный утилитный класс типа Collections, который бы предоставлял нужные обёртки. Но выглядело это как некий оверхэд. Поэтому были придуманы дефолтные методы для интерфейсов. Ну и туда же положили статические методы.

В контексте этого поста будет рассмотрен случай когда в родительских интерфейсах существуют методы с одинаковой сигнатурой.

Static

Начнем с простейшего. Со статических методов.

Предлагаю рассмотреть следующий код:

Есть два интерфейса с одним и тем же статическим методом. И класс, который имплементит эти два интерфейса. В данном случае Java не обязывает имплементить статический метод интерфейса. Мы можем добавить такой же статический метод в класс С и он никакого отношения не будет иметь к методам родительских интерфейсов. Собственно, работает так же само как и для обычных классов. Если мы попытаемся добавить аннотацию @Override к методу С.stat() получим ошибку: “The method stat() of type C must override or implement a supertype method”.

То есть статические методы можно использовать смело не боясь конфликтов в имплементациях.

Abstract methods

Рассмотрим следующий отрывок кода:

Всё как в предыдущем примере. Два одинаковых метода. Класс замечательно переопределяет этот метод. Кажется, ничего сложного нет и кейс проще, чем со статикой. Но давайте изменим возвращаемый тип метода в интерфейсе А. Получим ошибку при компиляции класса С: “The return type is incompatible with A.apply()”. Также интересная ситуация будет с исключением, если его нужно объявить в классе С. В таком случае это же исключение должно быть добавлено ко всем методам родительских интерфейсов. С другой стороны всё проще. Исключения могут быть объявлены в интерфейсах какие угодно и сколько угодно. Переопределяемый метод в классе может их полностью игноировать.

Получается, что абстрактные методы с одинаковой сигнатурой могут быть переопределны в классе только в том случае, если у них одинаковый возвращаемый тип и выбрасываемое исключение “покрывается” (поглощается родительским типом исключения или указано в родительском интерфейсе) всеми родительскими интерфейсами.

Default methods

Ну и наконец дефолтные методы:

Как можно видеть переопределение метода в классе С точно такое же как и у обычного абстрактного метода. Внутри метода класса С можно вызывать дефолтный метод из любого родительского интерфейса напрямую, а также можно имплементировать любую вашу логику.  Всё остальное также справедливо как и для абстрактных методов.