Реализация двух интерфейсов в классе с одним и тем же методом. Какой интерфейсный метод переопределен?

235

Два интерфейса с одинаковыми именами и сигнатурами методов. Но реализованный единственным классом, как компилятор определит, какой метод для какого интерфейса?

Пример:

interface A{
  int f();
}

interface B{
  int f();
}

class Test implements A, B{   
  public static void main(String... args) throws Exception{   

  }

  @Override
  public int f() {  // from which interface A or B
    return 0;
  }
}   
Jothi
источник

Ответы:

337

Если тип реализует два интерфейса, и каждый из них interfaceопределяет метод с одинаковой сигнатурой, то в действительности существует только один метод, и они не различимы. Если, скажем, два метода имеют конфликтующие типы возврата, то это будет ошибка компиляции. Это общее правило наследования, переопределения методов, скрытия и объявлений, а также применяется к возможным конфликтам не только между 2 унаследованными interfaceметодами, но также interfaceи к супер- classметоду, либо даже просто к конфликтам из-за стирания обобщенных типов.


Пример совместимости

Вот пример, где у вас есть метод interface Gift, у которого есть present()метод (например, для представления подарков), а также метод interface Guest, у которого также есть present()метод (например, гость присутствует, а не отсутствует).

Presentable johnnyэто Giftи а Guest.

public class InterfaceTest {
    interface Gift  { void present(); }
    interface Guest { void present(); }

    interface Presentable extends Gift, Guest { }

    public static void main(String[] args) {
        Presentable johnny = new Presentable() {
            @Override public void present() {
                System.out.println("Heeeereee's Johnny!!!");
            }
        };
        johnny.present();                     // "Heeeereee's Johnny!!!"

        ((Gift) johnny).present();            // "Heeeereee's Johnny!!!"
        ((Guest) johnny).present();           // "Heeeereee's Johnny!!!"

        Gift johnnyAsGift = (Gift) johnny;
        johnnyAsGift.present();               // "Heeeereee's Johnny!!!"

        Guest johnnyAsGuest = (Guest) johnny;
        johnnyAsGuest.present();              // "Heeeereee's Johnny!!!"
    }
}

Вышеуказанный фрагмент компилируется и запускается.

Обратите внимание, что есть только один @Override необходимый !!! , Это происходит потому , что Gift.present()и Guest.present()является « @Override-эквивалентны» ( JLS 8.4.2 ).

Таким образом, johnny имеет только одну реализацию из present(), и это не имеет значения , как вы относитесь johnny, то ли как Giftили как Guest, есть только один метод для вызова.


Пример несовместимости

Вот пример, где два унаследованных метода НЕ @Overrideэквивалентны:

public class InterfaceTest {
    interface Gift  { void present(); }
    interface Guest { boolean present(); }

    interface Presentable extends Gift, Guest { } // DOES NOT COMPILE!!!
    // "types InterfaceTest.Guest and InterfaceTest.Gift are incompatible;
    //  both define present(), but with unrelated return types"
}

Это также повторяет, что наследование членов от interfaceобязательного подчиняется общему правилу объявлений членов. Здесь мы имеем Giftи Guestопределяем present()с несовместимыми типами возврата: один voidдругой boolean. По той же причине, по которой вы не можете использовать void present()a и a boolean present()в одном типе, этот пример приводит к ошибке компиляции.


Резюме

Вы можете наследовать методы, которые @Overrideэквивалентны, при условии соблюдения обычных требований переопределения и сокрытия методов. Поскольку они ARE @Override -эквивалентны, эффективно существует только один способ реализации, и , таким образом , нет ничего , чтобы отличить / выбор.

Компилятору не нужно определять, какой метод предназначен для какого интерфейса, потому что, как только они определены как @Override-эквивалентные, они становятся одним и тем же методом.

Устранение потенциальных несовместимостей может быть сложной задачей, но это совсем другая проблема.

Ссылки

polygenelubricants
источник
Спасибо - это было полезно. Тем не менее, у меня возник еще один вопрос о несовместимости, который я опубликовал как новый вопрос
удивление
2
Кстати, это немного меняется с поддержкой defaultметодов в Java 8.
Питер Лори
Составные классы для устранения потенциальных несовместимостей могут быть хитростью :), но у меня никогда не было такой проблемы, и все же очевидно, что это может произойти.
Водолей Сила
1
В этой статье представлен шаблон проектирования, который можно использовать для некоторой ситуации, когда вам нужно реализовать два Colliding Interface, скажем, Fooи Bar. По сути, ваш класс реализует, скажем Foo, один из интерфейсов и предоставляет Bar asBar()метод для возврата внутреннего класса, который реализует второй Barинтерфейс. Не идеально, так как ваш класс в конечном итоге не является "баром", но в некоторых обстоятельствах он может быть полезен.
Javaru
1
Я - Java-разработчик, но c # действительно более умен в этом: stackoverflow.com/questions/2371178/…
Амир Зиарати
25

Этот вопрос был отмечен как дубликат этого вопроса /programming/24401064/understanding-and-solving-the-diamond-problems-in-java

Вам нужна Java 8, чтобы получить проблему множественного наследования, но она все еще не является проблемой diamon как таковой.

interface A {
    default void hi() { System.out.println("A"); }
}

interface B {
    default void hi() { System.out.println("B"); }
}

class AB implements A, B { // won't compile
}

new AB().hi(); // won't compile.

Как комментирует JB Nizet, вы можете исправить это, мое превосходство.

class AB implements A, B {
    public void hi() { A.super.hi(); }
}

Тем не менее, у вас нет проблем с

interface D extends A { }

interface E extends A { }

interface F extends A {
    default void hi() { System.out.println("F"); }
}

class DE implement D, E { }

new DE().hi(); // prints A

class DEF implement D, E, F { }

new DEF().hi(); // prints F as it is closer in the heirarchy than A.
Питер Лори
источник
Вот это да. это ново для меня. Почему они должны создать по умолчанию в Java 8?
Эрран Морад
1
Для облегчения добавления новых методов в интерфейсы (в частности, коллекции интерфейсов) без нарушения 60% кодовой базы.
Тассос Бассукос
@BoratSagdiyev Самая большая причина заключалась в том, чтобы поддержать закрытие и сделать его более полезным. Смотрите Collection.stream (). Взгляните на List.sort () docs.oracle.com/javase/8/docs/api/java/util/… Они добавили метод для всех списков без необходимости изменения какой-либо конкретной реализации. Они добавили Collection.removeIf (), что полезно
Питер Лоури,
@TassosBassoukos +1 говорят, что у вас есть собственная реализация List, теперь вы можете использовать myList.stream () it или myList.sort () без изменения кода
Питер Лоури,
3
@PeterLawrey: AB не будет компилироваться, потому что он должен переопределить hi()(чтобы исправить неоднозначность). Например, реализуя его, A.super.hi()чтобы выбрать для реализации так же, как А.
JB Nizet
20

Что касается компилятора, эти два метода идентичны. Там будет одна реализация обоих.

Это не проблема, если два метода эффективно идентичны, поскольку они должны иметь одинаковую реализацию. Если они отличаются по контракту (согласно документации для каждого интерфейса), у вас будут проблемы.

ясень
источник
2
Это объясняет, почему Java не позволяет вам продлевать более одного занятия
Артур Рональд,
1
@ArthurRonald, на самом деле это просто похоже на связь. Тем не менее, IMO, класс, который расширяет несколько классов, может столкнуться с проблемой Diamond (которая является дублированным состоянием объекта в наиболее производном классе), и именно поэтому Java убрала своих пользователей из проблем. С другой стороны, класс, который реализует более одного класса, никогда не может столкнуться с проблемой Diamond просто потому, что интерфейс не предоставляет состояния объектам. И проблема исключительно из-за синтаксических ограничений - невозможности полностью квалифицировать вызов функции.
августа
13

Там нет ничего, чтобы идентифицировать. Интерфейсы только запрещают имя метода и подпись. Если оба интерфейса имеют метод с одинаковым именем и сигнатурой, реализующий класс может реализовать оба метода интерфейса с помощью одного конкретного метода.

Однако, если семантические контракты метода двух интерфейсов противоречат, вы в значительной степени проиграли; тогда вы не можете реализовать оба интерфейса в одном классе.

Майкл Боргвардт
источник
4

Попробуйте реализовать интерфейс как анонимный.

public class MyClass extends MySuperClass implements MyInterface{

MyInterface myInterface = new MyInterface(){

/* Overrided method from interface */
@override
public void method1(){

}

};

/* Overrided method from superclass*/
@override
public void method1(){

}

}
dcanh121
источник
4

Как и в интерфейсе, мы просто объявляем методы, конкретный класс, который реализует оба эти интерфейса, понимает, что существует только один метод (как вы описали, оба имеют одинаковое имя в возвращаемом типе). поэтому не должно быть проблем с ним. Вы сможете определить этот метод в конкретном классе.

Но когда два интерфейса имеют метод с одинаковым именем, но с разным типом возврата, и вы реализуете два метода в конкретном классе:

Пожалуйста, посмотрите на код ниже:

public interface InterfaceA {
  public void print();
}


public interface InterfaceB {
  public int print();
}

public class ClassAB implements InterfaceA, InterfaceB {
  public void print()
  {
    System.out.println("Inside InterfaceA");
  }
  public int print()
  {
    System.out.println("Inside InterfaceB");
    return 5;
  }
}

когда компилятор получает метод "public void print ()", он сначала смотрит в InterfaceA и получает его. Но он все равно выдает ошибку времени компиляции, что возвращаемый тип не совместим с методом InterfaceB.

Так что это идет наперекосяк для компилятора.

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

Бхаграв Джайн
источник
3

Хорошо, если они оба одинаковы, это не имеет значения. Он реализует оба из них с одним конкретным методом для каждого метода интерфейса.

Пол Уилан
источник