Implementando dos interfaces en una clase con el mismo método. ¿Qué método de interfaz se anula?

235

Dos interfaces con los mismos nombres y firmas de métodos. Pero implementado por una sola clase, ¿cómo identificará el compilador qué método es para qué interfaz?

Ex:

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
fuente

Respuestas:

337

Si un tipo implementa dos interfaces, y cada interfaceuna define un método que tiene una firma idéntica, entonces, en efecto, solo hay un método, y no son distinguibles. Si, por ejemplo, los dos métodos tienen tipos de retorno en conflicto, entonces será un error de compilación. Esta es la regla general de herencia, anulación de métodos, ocultación y declaraciones, y se aplica también a posibles conflictos no solo entre 2 interfacemétodos heredados , sino también un interfacey un súper classmétodo, o incluso solo conflictos debido a la eliminación de tipos de genéricos.


Ejemplo de compatibilidad

Aquí hay un ejemplo en el que tiene un interface Gift, que tiene un present()método (como en, presentar regalos), y también un interface Guest, que también tiene un present()método (como en, el invitado está presente y no ausente).

Presentable johnnyes a la vez a Gifty 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!!!"
    }
}

El fragmento anterior se compila y se ejecuta.

Tenga en cuenta que solo @Override hay uno necesario !!! . Esto es porque Gift.present()y Guest.present()son " @Overrideequivalentes" ( JLS 8.4.2 ).

Por lo tanto, johnny solo tiene una implementación de present(), y no importa cómo se trate johnny, ya sea como Gifto como a Guest, solo hay un método para invocar.


Ejemplo de incompatibilidad

Aquí hay un ejemplo donde los dos métodos heredados NO son @Overrideequivalentes:

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

Esto reitera además que heredar miembros de un interfacedebe obedecer la regla general de las declaraciones de miembros. Aquí tenemos Gifty Guestdefinimos present()con tipos de retorno incompatibles: uno voidal otro boolean. Por la misma razón por la que no puede un void present()y un boolean present()tipo, este ejemplo da como resultado un error de compilación.


Resumen

Puede heredar métodos que sean @Overrideequivalentes, sujetos a los requisitos habituales de anulación y ocultación de métodos. Como SON @Override equivalentes, efectivamente solo hay un método para implementar, y por lo tanto no hay nada para distinguir / seleccionar.

El compilador no tiene que identificar qué método es para qué interfaz, porque una vez que se determina que son @Overrideequivalentes, son el mismo método.

Resolver incompatibilidades potenciales puede ser una tarea difícil, pero ese es otro problema por completo.

Referencias

poligenelubricantes
fuente
Gracias, esto fue útil. Sin embargo, tenía otra pregunta sobre incompatibilidad, que
publiqué
2
Por cierto, esto cambia un poco con el soporte de defaultmétodos en Java 8.
Peter Lawrey
Las clases compuestas para resolver posibles incompatibilidades pueden ser el truco :), pero nunca tuve ese problema, y ​​aún es evidente que puede suceder.
Acuario Power
1
Este artículo presenta un patrón de diseño que se puede utilizar para tratar de alguna manera la situación en la que necesita implementar dos interfaces de colisión, digamos Fooy Bar. Básicamente, su clase implementa una de las interfaces, por ejemplo Foo, y proporciona un Bar asBar()método para devolver una clase interna que implementa la segunda Barinterfaz. No es perfecto ya que su clase no es en última instancia "un bar", pero podría ser útil en algunas circunstancias.
Javaru
1
soy un desarrollador de Java pero C # es realmente más inteligente en esto: stackoverflow.com/questions/2371178/…
Amir Ziarati
25

Esto se marcó como un duplicado de esta pregunta /programming/24401064/understanding-and-solving-the-diamond-problems-in-java

Necesita Java 8 para obtener un problema de herencia múltiple, pero todavía no es un problema diamon como tal.

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.

Como comenta JB Nizet, puedes arreglar esto, mi anulación.

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

Sin embargo, no tienes ningún problema con

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
fuente
Guau. Esto es nuevo para mi. ¿Por qué tuvieron que crear por defecto en Java 8?
Erran Morad
1
Para facilitar la adición de nuevos métodos a las interfaces (específicamente las interfaces de colecciones) sin romper el 60% de la base de código.
Tassos Bassoukos
@BoratSagdiyev La razón más importante fue apoyar los cierres y hacer que los más útiles. Ver Collection.stream (). Eche un vistazo a List.sort () docs.oracle.com/javase/8/docs/api/java/util/… Han agregado un método para todas las Listas, sin tener que cambiar ninguna implementación específica.
Agregaron
@TassosBassoukos +1 dice que tiene su propia implementación de List, ahora puede myList.stream () it o myList.sort () sin cambiar su código
Peter Lawrey
3
@PeterLawrey: AB no compilará porque tiene que anular hi()(para corregir la ambigüedad). Por ejemplo, al implementarlo A.super.hi()para elegir implementarlo de la misma manera que A.
JB Nizet
20

En lo que respecta al compilador, esos dos métodos son idénticos. Habrá una implementación de ambos.

Esto no es un problema si los dos métodos son efectivamente idénticos, ya que deberían tener la misma implementación. Si son contractualmente diferentes (según la documentación de cada interfaz), tendrá problemas.

Ceniza
fuente
2
Explica por qué Java no le permite extender más de una clase
Arthur Ronald
1
@ ArthurRonald, en realidad solo parece relacionado. Sin embargo, IMO, la clase que se extiende más de una clase puede encontrarse con el Problema Diamante (que es el estado del objeto duplicado en la clase más derivada) y es muy probable que Java aleje a los usuarios de los problemas. Por otro lado, la clase que implementa más de una clase nunca puede encontrarse con el Problema Diamante simplemente porque la interfaz no proporciona estado a los objetos. Y el problema se debe únicamente a las limitaciones de sintaxis: incapacidad para calificar completamente la llamada a la función.
uvsmtid
13

No hay nada que identificar. Las interfaces solo prohíben el nombre y la firma de un método. Si ambas interfaces tienen un método exactamente del mismo nombre y firma, la clase implementadora puede implementar ambos métodos de interfaz con un único método concreto.

Sin embargo, si los contratos semánticos del método de dos interfaces son contradictorios, prácticamente has perdido; no puede implementar ambas interfaces en una sola clase entonces.

Michael Borgwardt
fuente
4

Intente implementar la interfaz como anónima.

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
fuente
4

Al igual que en la interfaz, solo estamos declarando métodos, la clase concreta que implementa estas dos interfaces entiende que solo hay un método (como usted describió, ambos tienen el mismo nombre en el tipo de retorno). así que no debería haber ningún problema con él. Podrá definir ese método en una clase concreta.

Pero cuando dos interfaces tienen un método con el mismo nombre pero diferente tipo de retorno e implementa dos métodos en una clase concreta:

Por favor, mire el siguiente código:

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

cuando el compilador obtiene el método "public void print ()", primero busca en InterfaceA y lo obtiene. Pero aún así da error de tiempo de compilación de que el tipo de retorno no es compatible con el método de InterfaceB.

Entonces se vuelve loco para el compilador.

De esta forma, no podrá implementar dos interfaces que tengan un método con el mismo nombre pero con un tipo de retorno diferente.

Bhagrav Jain
fuente
3

Bueno, si ambos son iguales, no importa. Implementa ambos con un único método concreto por método de interfaz.

Paul Whelan
fuente