В каком случае классу, реализующему два параметризованных интерфейса, приходится явно переопределять один и тот же default-метод, хотя после подстановки типов его сигнатуры совпадают?
Класс обязан явно переопределить метод, если два независимых интерфейса наследуют конфликтующие реализации default-метода с одной и той же сигнатурой после подстановки параметров типов. Компилятор не выбирает реализацию автоматически: класс должен явно разрешить конфликт.
Если один интерфейс является более специализированным наследником другого и его default-метод считается более специфичным, отдельное переопределение может не потребоваться.
Default-методы появились в Java 8, чтобы интерфейсы могли получать новые методы с реализацией без немедленного нарушения существующих классов. Однако при реализации нескольких интерфейсов возникает неоднозначность: у класса может оказаться несколько подходящих реализаций одного метода.
Generics добавляют к этому подстановку параметров типов. После подстановки разные объявления могут описывать один и тот же метод, поэтому компилятор проверяет не только исходные имена параметров типов, но и итоговую совместимость методов.
Рассмотрим класс, реализующий два интерфейса с одинаковым default-методом:
Без метода render в Document компилятор сообщит о конфликте default-методов. Автоматический выбор был бы опасен: порядок перечисления интерфейсов не задаёт приоритет, а выбор случайной реализации изменил бы поведение класса при добавлении нового интерфейса.
При проверке наследования Java сопоставляет методы по имени, типам параметров и правилам переопределения. После подстановки T типом String оба интерфейса предоставляют классу метод render(String), поэтому имеются две конкурирующие реализации.
Компилятор применяет правило наиболее специфичного интерфейса. Если один интерфейс наследует другой и переопределяет его default-метод, реализация более специализированного интерфейса имеет приоритет. Если интерфейсы независимы, приоритет отсутствует, и класс обязан предоставить собственную реализацию.
Собственная реализация может выбрать один из default-методов через квалифицированный вызов Интерфейс.super.метод(...), делегировать обоим интерфейсам или полностью реализовать новую политику. Это решение фиксируется в коде класса и становится частью его контракта.
Стирание типов важно для границ применимости: методы, различающиеся только параметрами типа, после стирания могут иметь одну JVM-сигнатуру. Поэтому нельзя рассчитывать, что виртуальная машина будет различать реализации по аргументам-типам. Разрешение конфликта выполняется компилятором на уровне исходной модели типов, а в байткоде остаётся однозначный метод класса.
В библиотеке есть интерфейс форматирования для CSV и интерфейс форматирования для JSON. Оба объявляют default-метод render(T), а прикладной класс реализует оба интерфейса для одного типа данных.
Вариант оставить конфликт не подходит: проект не скомпилируется. Вариант полагаться на порядок интерфейсов также неверен — Java не считает первый указанный интерфейс главным. Можно выбрать один default-метод, вызвать оба последовательно или написать независимую реализацию.
Наиболее безопасный вариант — явное переопределение в прикладном классе с документированным выбором формата. Это устраняет неоднозначность, делает поведение видимым для разработчиков и предотвращает незаметное изменение результата при расширении иерархии интерфейсов.
Достаточно ли совпадения возвращаемого типа для возникновения конфликта?
Нет. Метод определяется прежде всего именем и типами параметров; один только возвращаемый тип не может отличать перегрузки. Если default-методы имеют одинаковое имя и совместимые параметры, а возвращаемые типы допускают совместимое переопределение, конфликт может возникнуть. Несовместимые возвращаемые типы обычно приводят уже к другой ошибке — невозможности сформировать общий наследуемый метод.
Может ли класс вызвать конкретный default-метод напрямую через имя интерфейса?
Да, внутри реализации допустим квалифицированный вызов вида Интерфейс.super.метод(...), если этот интерфейс является прямым супер-интерфейсом класса и вызов не нарушает правила наследования. Это позволяет явно выбрать одну из реализаций, но не устраняет необходимость объявить метод класса.
Всегда ли параметризация делает методы разных интерфейсов разными?
Нет. Подстановка аргументов типа может, наоборот, привести методы к одной сигнатуре. Кроме того, после стирания типов параметры типа не представлены отдельными JVM-типами. Поэтому параметризация помогает статически проверять совместимость, но не создаёт независимые методы на уровне виртуальной машины.