Implementowanie dwóch interfejsów w klasie za pomocą tej samej metody. Która metoda interfejsu jest nadpisana?

235

Dwa interfejsy z tymi samymi nazwami metod i podpisami. Ale zaimplementowane przez jedną klasę, to w jaki sposób kompilator zidentyfikuje, która metoda jest dla którego interfejsu?

Dawny:

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
źródło

Odpowiedzi:

337

Jeśli typ implementuje dwa interfejsy, a każdy z nich interfacedefiniuje metodę o identycznej sygnaturze, to w efekcie istnieje tylko jedna metoda i nie można ich rozróżnić. Jeśli, powiedzmy, dwie metody mają sprzeczne typy zwrotów, oznacza to błąd kompilacji. Jest to ogólna zasada dziedziczenia, zastępowania metod, ukrywania i deklaracji, i ma zastosowanie również do ewentualnych konfliktów nie tylko między 2 odziedziczonymi interfacemetodami, ale także metodą interfacesuper i super class, a nawet po prostu konfliktów spowodowanych usunięciem typów ogólnych.


Przykład kompatybilności

Oto przykład, w którym masz metodę interface Gift, która ma present()metodę (jak w, prezentowanie prezentów), a także interface Guest, która również ma present()metodę (jak w, gość jest obecny i nieobecny).

Presentable johnnyjest zarówno Gifta 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!!!"
    }
}

Powyższy fragment kompiluje się i uruchamia.

Pamiętaj, że jest tylko jedna @Override konieczna !!! . To dlatego, że Gift.present()i Guest.present()są " @Override-equivalent" ( JLS 8.4.2 ).

Tak więc, johnny ma tylko jedną implementację o present(), i to nie ma znaczenia, w jaki sposób leczyć johnny, czy jako Giftlub jako Guest, że jest tylko jeden sposób, aby wywołać.


Przykład niezgodności

Oto przykład, w którym dwie odziedziczone metody NIE są @Overriderównoważne:

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

To jeszcze bardziej przypomina, że ​​dziedziczenie członków po konieczności interfacemusi być zgodne z ogólną zasadą deklaracji członków. Tutaj mamy Gifti Guestdefiniujemy present()niekompatybilne typy zwrotów: jeden voiddrugi boolean. Z tego samego powodu, że nie można void present()oraz boolean present()w jednym rodzaju, przykład ten powoduje błąd kompilacji.


Podsumowanie

Możesz dziedziczyć metody, które są @Overrideekwiwalentne, z zastrzeżeniem zwykłych wymagań zastępowania i ukrywania metod. Ponieważ @Override -equivalent, skutecznie istnieje tylko jeden sposób do wdrożenia, a więc nie ma nic do odróżnienia / wyboru.

Kompilator nie musi określać, która metoda jest przeznaczona dla @Overridedanego interfejsu, ponieważ po ustaleniu , że są równoważne, są one tą samą metodą.

Rozwiązanie potencjalnych niezgodności może być trudnym zadaniem, ale to zupełnie inna kwestia.

Bibliografia

środki smarujące wielotlenowe
źródło
Dzięki - to było pomocne. Miałem jednak kolejne pytanie dotyczące niezgodności, które zamieściłem jako nowe pytanie
wypłata w dniu
2
BTW To się trochę zmienia dzięki obsłudze defaultmetod w Javie 8.
Peter Lawrey
Klasy złożone w celu rozwiązania potencjalnych niezgodności mogą być sztuczką :), ale nigdy nie miałem takiego problemu i nadal jest oczywiste, że może się zdarzyć.
Wodnik Moc
1
W tym artykule przedstawiono wzorzec projektowy, którego można użyć, aby nieco poradzić sobie z sytuacją, w której trzeba zaimplementować dwa interfejsy kolidujące, powiedzmy Fooi Bar. Zasadniczo masz klasę, która implementuje jeden z interfejsów, powiedzmy Foo, i udostępnia Bar asBar()metodę zwracającą klasę wewnętrzną, która implementuje drugi Barinterfejs. Nie jest idealny, ponieważ twoja klasa ostatecznie nie jest „barem”, ale może być przydatna w niektórych okolicznościach.
Javaru
1
jestem programistą Java, ale c # jest naprawdę bardziej sprytny w tym przypadku: stackoverflow.com/questions/2371178/…
Amir Ziarati
25

Oznaczono to jako duplikat tego pytania /programming/24401064/understanding-and-solving-the-diamond-problems-in-java

Potrzebujesz Java 8, aby uzyskać problem wielokrotnego dziedziczenia, ale nadal nie jest to problem diamon jako taki.

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.

Jak komentuje JB Nizet, możesz to naprawić.

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

Jednak nie masz z tym problemu

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.
Peter Lawrey
źródło
łał. To jest dla mnie nowość. Dlaczego musieli stworzyć domyślną wersję w Javie 8?
Erran Morad
1
Aby ułatwić dodawanie nowych metod do interfejsów (w szczególności interfejsów kolekcji) bez zerwania 60% bazy kodu.
Tassos Bassoukos
@BoratSagdiyev Największym powodem było wspieranie zamknięć i uczynienie bardziej użytecznym. Zobacz Collection.stream (). Zajrzyj na List.sort () docs.oracle.com/javase/8/docs/api/java/util/... Dodali metodę dla wszystkich list, bez konieczności zmiany jakiejkolwiek konkretnej implementacji. Dodali Collection.removeIf (), co jest przydatne
Peter Lawrey
@TassosBassoukos +1 mówią, że masz własną implementację List, teraz możesz myList.stream () it lub myList.sort () it bez zmiany kodu
Peter Lawrey
3
@PeterLawrey: AB nie skompiluje się, ponieważ musi przesłonić hi()(aby naprawić niejednoznaczność). Na przykład, wdrażając go, A.super.hi()aby wybrać wdrożenie w taki sam sposób, jak A.
JB Nizet
20

Jeśli chodzi o kompilator, te dwie metody są identyczne. Będzie jedno wdrożenie obu.

Nie stanowi to problemu, jeśli dwie metody są faktycznie identyczne, ponieważ powinny mieć tę samą implementację. Jeśli różnią się w umowie (zgodnie z dokumentacją dla każdego interfejsu), będziesz miał kłopoty.

Popiół
źródło
2
Wyjaśnia, dlaczego Java nie pozwala na przedłużenie więcej niż jednej klasy
Arthur Ronald
1
@ArthurRonald, tak naprawdę wygląda to na powiązane. Jednak IMO, klasa, która rozciąga się na więcej niż jedną klasę, może napotkać Diamentowy Problem (który jest zduplikowanym stanem obiektu w klasie najbardziej pochodnej) i najprawdopodobniej Java odciągnęła jego użytkowników od problemów. Z drugiej strony klasa, która implementuje więcej niż jedną klasę, nigdy nie może napotkać Diamentowego Problemu po prostu dlatego, że interfejs nie zapewnia stanu obiektów. Problem wynika wyłącznie z ograniczeń składniowych - niemożności pełnego zakwalifikowania wywołania funkcji.
uvsmtid
13

Nie ma nic do zidentyfikowania. Interfejsy zakazują jedynie nazwy metody i podpisu. Jeśli oba interfejsy mają metodę o dokładnie takiej samej nazwie i podpisie, klasa implementująca może zaimplementować obie metody interfejsu za pomocą jednej konkretnej metody.

Jeśli jednak kontrakty semantyczne metody dwóch interfejsów są ze sobą sprzeczne, prawie się zgubiłeś; nie można wtedy wdrożyć obu interfejsów w jednej klasie.

Michael Borgwardt
źródło
4

Spróbuj zaimplementować interfejs jako anonimowy.

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
źródło
4

Podobnie jak w interfejsie, po prostu deklarujemy metody, konkretna klasa, która implementuje oba interfejsy rozumie, że istnieje tylko jedna metoda (jak opisano, obie mają taką samą nazwę w typie zwracanym). więc nie powinno być z tym problemu. Będziesz mógł zdefiniować tę metodę w konkretnej klasie.

Ale gdy dwa interfejsy mają metodę o tej samej nazwie, ale innym typie zwrotu, a implementujesz dwie metody w konkretnej klasie:

Proszę spojrzeć na poniższy kod:

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

kiedy kompilator otrzyma metodę „public void print ()”, najpierw szuka w interfejsie A i dostaje go, ale nadal daje błąd czasu kompilacji, że typ zwracany jest niezgodny z metodą interfejsuB.

Więc idzie na kompilator.

W ten sposób nie będzie można wdrożyć dwóch interfejsów o metodzie o tej samej nazwie, ale o innym typie zwrotu.

Bhagrav Jain
źródło
3

Cóż, jeśli oba są takie same, to nie ma znaczenia. Obie implementują je za pomocą jednej konkretnej metody dla każdej metody interfejsu.

Paul Whelan
źródło