В случае конфликта между унаследованным методом класса и default-методом интерфейса какая реализация будет вызвана?
Реализация класса имеет приоритет над default-методом интерфейса. Если класс или его суперкласс предоставляет подходящий экземплярный метод, Java использует его, а default-реализация не выбирается автоматически.
Если подкласс переопределяет этот метод, при обычном виртуальном вызове будет выбрана реализация подкласса. default-метод становится кандидатом только при отсутствии подходящей реализации в иерархии классов.
default-методы появились в Java 8, чтобы интерфейсы можно было развивать без обязательного добавления реализации во все существующие классы. Интерфейс получил возможность содержать готовое поведение, сохраняя совместимость с уже написанными реализациями.
Однако добавление default-методов не должно было неожиданно менять поведение классов, у которых уже есть реализация того же метода. Поэтому в правилах разрешения методов закреплён приоритет класса над интерфейсом.
Представим, что базовый класс уже содержит метод, а новый интерфейс добавляет default-метод с такой же сигнатурой. Если бы интерфейс автоматически имел равный или больший приоритет, подключение интерфейса могло бы изменить поведение существующего класса.
Главный риск — ошибочно ожидать вызов интерфейсного default-метода только потому, что объект также реализует интерфейс. На практике сначала учитывается иерархия классов, а затем — методы интерфейсов, которые не были перекрыты классом.
При разрешении экземплярного метода Java сначала рассматривает методы класса и его суперклассов. Если найден подходящий метод, он имеет приоритет над default-методами интерфейсов. Затем обычная виртуальная диспетчеризация может выбрать переопределение в фактическом классе объекта.
В примере Job реализует Worker, но наследует run от Base. Поэтому результатом будет base, а не interface. Если Job объявит собственный run, будет вызвана его реализация.
Это правило относится к совместимым экземплярным методам. static-методы не переопределяются, а private-методы не наследуются как доступные для переопределения. Если класс объявляет абстрактный метод с такой сигнатурой, default-метод интерфейса не превращает абстрактный класс в конкретный: конкретный подкласс всё равно должен предоставить реализацию.
Если подходящей реализации в классе нет, Java может использовать единственный совместимый default-метод интерфейса. При конфликте нескольких независимых default-методов класс должен устранить неоднозначность, обычно объявив собственный метод.
В библиотеке есть базовый класс AbstractTask с методом execute, который учитывает транзакции. Позднее к API добавляют интерфейс Retryable с default-реализацией повторных попыток. Класс-наследник одновременно расширяет AbstractTask и реализует Retryable.
Возможны два подхода. Можно положиться на приоритет класса: это сохраняет старое транзакционное поведение, но повторные попытки из интерфейса не включатся, что легко не заметить. Можно явно переопределить execute в наследнике и согласованно объединить транзакционную логику с повторными попытками; это прозрачнее, но требует аккуратно определить порядок действий, обработку исключений и границы транзакции.
Практически безопаснее явно переопределить метод в адаптере, если нужны обе политики. В результате поведение не зависит от неочевидного правила разрешения методов, а тесты могут отдельно проверить транзакции, повторы и их взаимодействие.
Может ли default-метод интерфейса заменить реализацию, унаследованную от суперкласса?
Нет. Наличие default-метода не отменяет метод из иерархии классов. Даже если интерфейс подключён позднее или ссылка имеет тип интерфейса, реализация класса сохраняет приоритет при вызове совместимого экземплярного метода.
Что произойдёт, если базовый класс объявляет этот метод абстрактным, а интерфейс предоставляет default-реализацию?
Абстрактное объявление класса не считается выполненной реализацией. Конкретный подкласс должен реализовать метод сам; default-метод интерфейса не устраняет требование абстрактного суперкласса. Это важно, потому что контракт класса и контракт интерфейса разрешаются не как простая замена одного метода другим.
Почему подключение интерфейса с default-методом не должно менять поведение старого класса?
Потому что Java отдаёт приоритет классу. Такой порядок поддерживает предсказуемость наследования: существующая реализация класса продолжает работать, даже если класс начинает реализовывать новый интерфейс с одноимённым default-методом. Иначе расширение интерфейса могло бы незаметно изменить семантику уже работающего кода.