Implementando duas interfaces em uma classe com o mesmo método. Qual método de interface é substituído?

235

Duas interfaces com os mesmos nomes e assinaturas de método. Mas, implementado por uma única classe, como o compilador identificará qual método é para qual interface?

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
fonte

Respostas:

337

Se um tipo implementa duas interfaces, e cada interfaceuma define um método que possui assinatura idêntica, na verdade, existe apenas um método e elas não são distinguíveis. Se, por exemplo, os dois métodos tiverem tipos de retorno conflitantes, será um erro de compilação. Essa é a regra geral de herança, substituição de método, ocultação e declaração, e se aplica também a possíveis conflitos não apenas entre 2 interfacemétodos herdados , mas também um método interfacee um super class, ou mesmo apenas conflitos devido ao apagamento de tipo de genéricos.


Exemplo de compatibilidade

Aqui está um exemplo em que você tem um interface Gift, que tem um present()método (como em apresentar presentes), e também um interface Guest, que também tem um present()método (como em, o convidado está presente e não está ausente).

Presentable johnnyé a Gifte 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!!!"
    }
}

O trecho acima compila e executa.

Observe que há apenas um @Override necessário !!! . Isso ocorre Gift.present()e Guest.present()é " @Override-equivalente" ( JLS 8.4.2 ).

Assim, johnny só tem uma implementação de present(), e não importa como você trata johnny, seja como Giftou como Guest, só há um método a ser chamado.


Exemplo de incompatibilidade

Aqui está um exemplo em que os dois métodos herdados NÃO são @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"
}

Isso reitera ainda que a herança de membros de um interfacedeve obedecer à regra geral das declarações de membros. Aqui temos Gifte Guestdefinimos present()com tipos de retorno incompatíveis: um voido outro boolean. Pelo mesmo motivo que você não pode um void present()e um boolean present()em um tipo, este exemplo resulta em um erro de compilação.


Resumo

Você pode herdar métodos que sejam @Overrideequivalentes, sujeitos aos requisitos usuais de substituição e ocultação de métodos. Como são @Override equivalentes, efetivamente existe apenas um método para implementar e, portanto, não há nada para distinguir / selecionar.

O compilador não precisa identificar qual método é para qual interface, porque uma vez que eles são considerados @Overrideequivalentes, eles são o mesmo método.

Resolver possíveis incompatibilidades pode ser uma tarefa complicada, mas isso é outra questão.

Referências

poligenelubricants
fonte
Obrigado - isso foi útil. No entanto, eu tinha uma outra pergunta sobre incompatibilidade, que eu
publiquei
2
BTW Isso muda um pouco com o apoio de defaultmétodos em Java 8.
Peter Lawrey
Classes compostas para resolver possíveis incompatibilidades podem ser o truque :), mas nunca tive esse problema e ainda assim é evidente que pode acontecer.
Aquário Potência
1
Este artigo apresenta um padrão de design que podem ser usados para um pouco lidar com a situação em que você precisa implementar dois Colisão Interface, dizem Fooe Bar. Basicamente, sua classe implementa uma das interfaces, digamos Foo, e fornece um Bar asBar()método para retornar uma classe interna que implementa a segunda Barinterface. Não é perfeito, pois sua classe não é "um bar", mas pode ser útil em algumas circunstâncias.
Javaru
1
im um desenvolvedor java, mas c # é realmente mais inteligente sobre isso: stackoverflow.com/questions/2371178/...
Amir Ziarati
25

Isso foi marcado como uma duplicata para esta pergunta /programming/24401064/understanding-and-solving-the-diamond-problems-in-java

Você precisa do Java 8 para obter um problema de herança múltipla, mas ainda assim não é um problema diário.

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, você pode corrigir isso, minha substituição.

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

No entanto, você não tem problemas com

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
fonte
Uau. Isso é novo para mim. Por que eles tiveram que criar o padrão no java 8?
Erran Morad
1
Facilitar a adição de novos métodos a interfaces (especificamente interfaces de coleções) sem quebrar 60% da base de código.
Tassos Bassoukos
@BoratSagdiyev O maior motivo foi apoiar fechamentos e tornar os mais úteis. Veja Collection.stream (). Dê uma olhada em List.sort () docs.oracle.com/javase/8/docs/api/java/util/… Eles adicionaram um método para todas as listas, sem precisar alterar nenhuma implementação específica. Eles adicionaram Collection.removeIf () que é útil #
Peter Lawrey
@TassosBassoukos +1 dizer que você tem sua própria implementação da lista, agora você pode myList.stream () ou myList.sort () sem alterar o seu código
Peter Lawrey
3
@ PeterLawrey: A AB não compila porque precisa substituir hi()(para corrigir a ambiguidade). Por exemplo, através da implementação lo como A.super.hi()escolher implementá-lo da mesma maneira como A.
JB Nizet
20

No que diz respeito ao compilador, esses dois métodos são idênticos. Haverá uma implementação de ambos.

Isso não é um problema se os dois métodos forem efetivamente idênticos, pois devem ter a mesma implementação. Se eles forem contratualmente diferentes (conforme a documentação de cada interface), você estará com problemas.

Cinza
fonte
2
Isso explica porque Java não permite que você se estende mais de uma classe
Arthur Ronald
1
@ArthurRonald, na verdade, apenas parece relacionado. No entanto, a IMO, classe que estende mais de uma classe, pode ser executada no Diamond Problem (que é o estado duplicado do objeto na classe mais derivada) e é por isso que o Java afastou seus usuários dos problemas. Por outro lado, a classe que implementa mais de uma classe nunca pode ser executada no Diamond Problem simplesmente porque a interface não fornece estado aos objetos. E o problema é puramente devido a limitações de sintaxe - incapacidade de qualificar totalmente a chamada de função.
Uvsmtid
13

Não há nada para identificar. As interfaces apenas proíbem o nome e a assinatura de um método. Se ambas as interfaces tiverem um método exatamente com o mesmo nome e assinatura, a classe de implementação poderá implementar os dois métodos de interface com um único método concreto.

No entanto, se os contratos semânticos do método de duas interfaces estiverem em contradição, você praticamente perderá; você não pode implementar as duas interfaces em uma única classe então.

Michael Borgwardt
fonte
4

Tente implementar a interface 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
fonte
4

Como na interface, estamos apenas declarando métodos, a classe concreta que implementa essas duas interfaces entende que existe apenas um método (como você descreveu, ambos têm o mesmo nome no tipo de retorno). portanto, não deve haver problema. Você poderá definir esse método na classe concreta.

Mas quando duas interfaces têm um método com o mesmo nome, mas com um tipo de retorno diferente e você implementa dois métodos na classe concreta:

Por favor, veja o código abaixo:

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

quando o compilador obtém o método "public void print ()", ele procura pela primeira vez na InterfaceA e o obtém. Mas, ainda assim, gera um erro de tempo de compilação que o tipo de retorno não é compatível com o método da InterfaceB.

Por isso, dá errado para o compilador.

Dessa forma, você não poderá implementar duas interfaces com um método com o mesmo nome, mas com um tipo de retorno diferente.

Bhagrav Jain
fonte
3

Bem, se ambos são iguais, não importa. Ele implementa os dois com um único método concreto por método de interface.

Paul Whelan
fonte