Странное поведение множественного наследования - Java 8

В следующем коде, если я раскомментирую I3 и реализую I2 и I3, произойдет сбой compilation со следующей ошибкой:

несвязанные значения по умолчанию для m2() из I3 и I2

Что является прекрасным и ожидаемым поведением.

Однако, когда я заменяю I3 на I, он успешно компилируется, и я получаю I2 в качестве вывода.

public class DefaultMethodTest implements I, I2 {
    public static void main(String[] args) {
        DefaultMethodTest obj = new DefaultMethodTest();
        obj.m2();
     }
}
    
interface I {
        default void m2() {
        System.out.println("I1");
    }
}

interface I2 extends I {
        default void m2() {
        System.out.println("I2");
    }
}

//interface I3 extends I {
//
//    default void m2() {
//        System.out.println("I3");
//    }
//}

Теперь у меня есть пара вопросов здесь:

  • Почему во втором случае нет ошибки compilation, хотя оба интерфейса имеют один и тот же метод по умолчанию m2?

  • Почему I2 имеет приоритет над I?.

Примечание. Этот вопрос не связан с java-8-default-method-inheritance


person Sachin Sachdeva    schedule 21.06.2020    source источник
comment
В следующем коде, если я раскомментирую I3, произойдет сбой компиляции. Я не могу воспроизвести эту проблему. Если я только раскомментирую I3, то ошибка компиляции не должна появиться, так как I3 не используется в public class DefaultMethodTest implements I, I2 {..}. Ваш пример кода, вероятно, должен включать I3 в DefaultMethodTest, например public class DefaultMethodTest implements I, I2, I3 {..} или public class DefaultMethodTest implements I2, I3{..}.   -  person Pshemo    schedule 21.06.2020
comment
Кстати, System.out.println("I2"); внутри I3, вероятно, должно быть System.out.println("I3"); для ясности.   -  person Pshemo    schedule 21.06.2020
comment
@Pshemo Готово. Благодарность   -  person Sachin Sachdeva    schedule 22.06.2020


Ответы (1)


В соответствии с правилами разрешения методов по умолчанию будет выбран метод по умолчанию в наиболее специфичном интерфейсе, предоставляющем значения по умолчанию. В вашем случае и у I, и у I2 есть метод по умолчанию (каждый) с одинаковой подписью. Следовательно, он выберет I2, который является вашим наиболее конкретным интерфейсом по умолчанию.

С другой стороны, если вы implement I2, I3, возникнет конфликт, который вам необходимо разрешить (путем реализации m2 в вашем классе).

Использованная литература:

http://www.lambdafaq.org/how-are-conflicting-method-declarations-resolved/ https://javadevcentral.com/default-method-resolution-rules

person user7    schedule 21.06.2020
comment
почему нет ошибки компиляции?? - person Sachin Sachdeva; 21.06.2020
comment
Почему он не сможет компилироваться, когда I2 побеждает I? - person user7; 21.06.2020
comment
У меня нет удобной ссылки JLS, но это связано с наследованием между I и дочерними классами, которое теряется, когда вы наследуете два иерархически эквивалентных класса (I2 и I3). У одного есть более конкретный метод (это дочерняя реализация), а у другого два одинаково неоднозначных. Это также хорошая причина для явного переопределения, когда у вас есть два унаследованных метода с совпадающими сигнатурами, даже если просто вызвать один из них через IParent.super.myMethod() - person Rogue; 21.06.2020
comment
@Rogue Это не имеет ничего общего с I. Даже если бы I не было, возник бы конфликт при реализации I2 и I3. - person user7; 21.06.2020
comment
Я говорю о том, почему I с дочерним классом было разрешено, а не о том, почему другой ошибся. Я думаю, мы спорим об одном и том же (метод/резолюция члена) - person Rogue; 21.06.2020
comment
@Rogue Когда у нас есть I -> I2, I2 более конкретен, тогда как когда у нас есть I -> I2 и I -> I3 и мы реализуем I2 и I3, возникает конфликт. Я думаю, мы говорим об одном и том же :) - person user7; 21.06.2020
comment
Я не уверен, что наиболее конкретный интерфейс предоставления по умолчанию является правильным термином здесь. Я думал, что это означает нечто большее, чем foo(Object o){...} foo(Number n){...}, и если я использую foo(1), то будет выполнено foo(Number), потому что Number является более конкретным описанием для Integer, чем Object. Но в случае class Parent{void foo(){..}} class Child extends Parent {@override void foo(){..} } class GrandChild extends Child{} GrandChild gc = new GrandChild(); gc.foo(); будет вызван foo из Child, поскольку он находится ближе всего в дереве наследования. - person Pshemo; 21.06.2020
comment
Также в случае, когда interface I2 extends I объявление I в ... implements I, I2 является избыточным. Это эффективно implements I2. - person Pshemo; 21.06.2020
comment
Переопределения методов @Pshemo разрешаются во время компиляции. Дело не в этом. Это когда класс наследует более одного метода с одной и той же сигнатурой. - person user7; 21.06.2020