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


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;
  }
}   

Відповіді:


337

Якщо тип реалізує два інтерфейси, і кожен interfaceвизначає метод, що має ідентичну підпис, то по суті існує лише один метод, і вони не відрізняються. Якщо, скажімо, два способи мають суперечливі типи повернення, то це буде помилка компіляції. Це загальне правило успадкування, переосмислення методу, приховування та декларації, і стосується також можливих конфліктів не лише між двома успадкованими interfaceметодами, але також interfaceі супер classметодом, або навіть просто конфліктами через стирання типу дженериків.


Приклад сумісності

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

Presentable johnnyє і a, Giftі a 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()і boolean present()один тип, цей приклад призводить до помилки компіляції.


Підсумок

Ви можете успадковувати методи, які є @Overrideеквівалентними, за умови звичних вимог методу, що перекриває та приховує. Оскільки вони НЕ @Override еквівалентні, фактично існує лише один метод реалізації, і, отже, немає чого відрізняти / вибирати.

Компілятору не потрібно визначати, який метод є для якого інтерфейсу, тому що коли вони визначаються як @Override-еквівалентні, вони є тим самим методом.

Вирішення потенційної несумісності може бути складним завданням, але це зовсім інше питання.

Список літератури


Спасибі - це було корисно. Однак у мене виникло ще одне питання щодо несумісності, яке я опублікував як нове запитання
подив

2
BTW Це трохи зміниться за підтримки defaultметодів у Java 8.
Пітер Лоурі,

Композиційні класи для вирішення потенційної несумісності можуть бути хитрістю :), але я ніколи не мав такої проблеми, і все одно очевидно, що це може статися.
Сила Водолія

1
У цій статті представлений шаблон дизайну, який може бути використаний, щоб дещо впоратися з ситуацією, коли потрібно реалізувати два інтерфейси, що стикаються, скажімо, Fooі Bar. В основному, ваш клас реалізує один з інтерфейсів, скажімо Foo, і надає Bar asBar()метод для повернення внутрішнього класу, який реалізує другий Barінтерфейс. Не ідеально, оскільки ваш клас в кінцевому рахунку не є "баром", але він може бути корисним за деяких обставин.
Javaru

1
їм розробник Java , але з # дійсно розумніший на цьому: stackoverflow.com/questions/2371178 / ...
Amir Ziarati

25

Це було позначено як дублікат цього питання /programming/24401064/understanding-and-solving-the-diamond-problems-in-java

Вам потрібна Java 8, щоб отримати множинну проблему успадкування, але це все ще не проблема діамону як такої.

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 скажіть, що у вас є власна реалізація Список, тепер ви можете myList.stream () це або myList.sort () це, не змінюючи код
Пітер Лорі

3
@PeterLawrey: AB не буде компілюватися, оскільки він повинен переосмислити hi()(виправити неоднозначність). Наприклад, застосувавши його так, як A.super.hi()вирішили реалізувати його так само, як і А.
JB Nizet

20

Що стосується компілятора, то ці два методи однакові. Буде одна реалізація обох.

Це не є проблемою, якщо два способи фактично однакові, оскільки вони повинні мати однакову реалізацію. Якщо вони контрактно відрізняються (відповідно до документації для кожного інтерфейсу), ви будете мати проблеми.


2
Це пояснює, чому Java не дозволяє вам розширити більш ніж один клас
Артур Рональд

1
@ArthurRonald, насправді це просто схоже. Однак IMO, клас, який розширює більше одного класу, може потрапити в Diamond Problem (який дублюється станом об'єкта в найбільш похідному класі), і саме це, швидше за все, Java відібрало своїх користувачів від неприємностей. З іншого боку, клас, який реалізує більше одного класу, ніколи не може потрапити в Diamond Problem просто тому, що інтерфейс не забезпечує стан об'єктів. І проблема полягає виключно в синтаксичному обмеженні - неможливості повноцінно кваліфікувати виклик функції.
uvsmtid

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(){

}

}

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

Добре, якщо вони обидва однакові, це не має значення. Вони реалізують обидва з використанням одного конкретного методу на метод інтерфейсу.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.