Por que não há herança múltipla em Java, mas a implementação de múltiplas interfaces é permitida?

153

Java não permite herança múltipla, mas permite implementar várias interfaces. Por quê?

abson
fonte
1
Editei o título da pergunta para torná-la mais descritiva.
Bozho 25/03/10
4
Curiosamente, no JDK 8, haverá métodos de extensão que permitirão a definição da implementação de métodos de interface. As regras foram definidas para governar a herança múltipla do comportamento, mas não do estado (o que eu entendo é mais problemático .
Edwin Dalorzo
1
Antes que vocês percam tempo com respostas que apenas dizem "Como o Java alcança herança múltipla" ... sugiro que você vá até a resposta de @Prabal Srivastava que logicamente fornece o que deve estar acontecendo internamente, para não permitir que as aulas sejam corretas .. e apenas permitem às interfaces o direito de permitir herança múltipla.
Yo Apps

Respostas:

228

Como as interfaces especificam apenas o que a classe está fazendo, não como está fazendo.

O problema da herança múltipla é que duas classes podem definir maneiras diferentes de fazer a mesma coisa, e a subclasse não pode escolher qual escolher.

Bozho
fonte
8
Eu costumava fazer C ++ e tive exatamente esse mesmo problema algumas vezes. Recentemente, li sobre o Scala ter "características" que, para mim, parecem algo entre o modo "C ++" e o modo "Java" de fazer as coisas.
Niels Basjes
2
Desta forma, você está evitando o "problema diamante": en.wikipedia.org/wiki/Diamond_problem#The_diamond_problem
Nick L.
6
Desde o Java 8, você pode definir dois métodos padrão idênticos, um em cada interface. Se você vai implementar ambas as interfaces em sua classe, você tem que substituir esse método na própria classe, consulte: docs.oracle.com/javase/tutorial/java/IandI/...
bobbel
6
Esta resposta não é precisa. O problema não é especificar como e o Java 8 existe para provar. No Java 8, duas super interfaces podem declarar o mesmo método com implementações diferentes, mas isso não é um problema, porque os métodos de interface são virtuais , portanto você pode substituí-los e o problema é resolvido. O problema real é a ambiguidade nos atributos , porque você não pode resolver essa ambiguidade substituindo o atributo (os atributos não são virtuais).
Alex Oliveira
1
O que você quer dizer com "atributos"?
Bozho 22/07/16
96

Um dos meus instrutores da faculdade me explicou da seguinte maneira:

Suponha que eu tenha uma classe, que é uma torradeira, e outra classe, que é a NuclearBomb. Ambos podem ter uma configuração de "escuridão". Ambos têm um método on (). (Um está desativado (), o outro não.) Se eu quiser criar uma classe que seja uma subclasse de ambos ... como você pode ver, esse é um problema que pode realmente explodir na minha cara aqui .

Portanto, um dos principais problemas é que, se você tiver duas classes principais, elas podem ter implementações diferentes do mesmo recurso - ou possivelmente dois recursos diferentes com o mesmo nome, como no exemplo do meu instrutor. Então você tem que lidar com a decisão de qual delas sua subclasse vai usar. Existem maneiras de lidar com isso, certamente - o C ++ faz isso -, mas os designers de Java acharam que isso tornaria as coisas muito complicadas.

Com uma interface, porém, você está descrevendo algo que a classe é capaz de fazer, em vez de emprestar o método de outra classe para fazer alguma coisa. É muito menos provável que várias interfaces causem conflitos complicados que precisam ser resolvidos do que várias classes-pai.

Sintático
fonte
5
Obrigado, NomeN. A fonte da citação era um estudante de doutorado chamado Brendan Burns, que na época também era o mantenedor do repositório de código-fonte aberto do Quake 2. Vai saber.
Syntactic
4
O problema com essa analogia é que, se você estiver criando uma subclasse de uma bomba nuclear e uma torradeira, a "torradeira nuclear" explodiria razoavelmente quando usada.
101100 27/03
20
Se alguém decide misturar uma bomba nuclear e uma torradeira, ele merece que a bomba exploda em seu rosto. A citação é raciocínio falacioso
Masoud
1
A mesma linguagem de programação permite herança múltipla de "interface", portanto, essa "justificativa" não se aplica.
Curiousguy
24

Como a herança é usada em excesso, mesmo quando você não pode dizer "ei, esse método parece útil, estenderei essa classe também".

public class MyGodClass extends AppDomainObject, HttpServlet, MouseAdapter, 
             AbstractTableModel, AbstractListModel, AbstractList, AbstractMap, ...
Michael Borgwardt
fonte
Você pode explicar por que diz que a herança é usada em excesso? Criar uma classe divina é exatamente o que eu quero fazer! Eu encontro muitas pessoas que trabalham com herança única criando classes "Ferramentas" que possuem métodos estáticos.
Duncan Calvert
9
@DuncanCalvert: Não, você não deseja fazer isso, não se esse código precisar de manutenção. Muitos métodos estáticos perdem o objetivo do OO, mas a herança múltipla excessiva é muito pior porque você perde completamente o controle de qual código é usado onde e como é conceitualmente uma classe. Ambos estão tentando resolver o problema de "como posso usar esse código onde preciso dele", mas esse é um problema simples de curto prazo. O muito mais difícil a longo prazo problema resolvido por bom design OO é "como faço para mudar este código sem ter a quebra programa em 20 lugares diferentes de maneiras imprevisíveis?
Michael Borgwardt
2
@DuncanCalvert: e você resolve isso tendo classes com alta coesão e baixo acoplamento, o que significa que elas contêm partes de dados e códigos que interagem intensamente entre si, mas que interagem com o restante do programa apenas por meio de uma API pública pequena e simples. Em seguida, você pode pensar neles em termos dessa API, e não nos detalhes internos, o que é importante porque as pessoas só conseguem manter uma quantidade limitada de detalhes ao mesmo tempo.
Michael Borgwardt
18

A resposta desta pergunta está no trabalho interno do compilador java (encadeamento de construtor). Se vemos o funcionamento interno do compilador java:

public class Bank {
  public void printBankBalance(){
    System.out.println("10k");
  }
}
class SBI extends Bank{
 public void printBankBalance(){
    System.out.println("20k");
  }
}

Depois de compilar esta aparência:

public class Bank {
  public Bank(){
   super();
  }
  public void printBankBalance(){
    System.out.println("10k");
  }
}
class SBI extends Bank {
 SBI(){
   super();
 }
 public void printBankBalance(){
    System.out.println("20k");
  }
}

quando estendemos a classe e criamos um objeto, uma cadeia de construtores será executada até a Objectclasse.

O código acima funcionará bem. mas se tivermos outra classe chamada Carque se estende Banke uma classe híbrida (herança múltipla) chamada SBICar:

class Car extends Bank {
  Car() {
    super();
  }
  public void run(){
    System.out.println("99Km/h");
  }
}
class SBICar extends Bank, Car {
  SBICar() {
    super(); //NOTE: compile time ambiguity.
  }
  public void run() {
    System.out.println("99Km/h");
  }
  public void printBankBalance(){
    System.out.println("20k");
  }
}

Nesse caso, o SBICar falhará ao criar a cadeia de construtores ( ambiguidade do tempo de compilação ).

Para interfaces, isso é permitido porque não podemos criar um objeto.

Para novos conceitos defaulte staticmétodos, por favor, consulte o padrão na interface .

Espero que isso resolva sua consulta. Obrigado.

Prabal Srivastava
fonte
7

A implementação de múltiplas interfaces é muito útil e não causa muitos problemas para implementadores de linguagem nem programadores. Então é permitido. A herança múltipla, embora útil, pode causar sérios problemas aos usuários (temido diamante da morte ). E a maioria das coisas que você faz com herança múltipla também pode ser feita por composição ou usando classes internas. Portanto, a herança múltipla é proibida por trazer mais problemas do que ganhos.

Tadeusz Kopec
fonte
Qual é o problema com o "diamante da morte"?
Curiousguy
2
@curiousguy Contendo mais de um subobjeto da mesma classe base, ambiguidade (substituição da qual a classe base usa), regras complicadas para resolver essa ambiguidade.
Tadeusz Kopec
@curiousguy: Se uma estrutura prevê que a conversão de uma referência de objeto para uma referência de tipo base preservará a identidade, toda instância de objeto deverá ter exatamente uma implementação de qualquer método de classe base. Se ToyotaCare HybridCarambos derivassem Care substituíssemCar.Drive , e se PriusCarherdassem ambos, mas não substituíssemDrive , o sistema não teria como identificar o que o virtual Car.Drivedeveria fazer. As interfaces evitam esse problema, evitando a condição em itálico acima.
Supercat
1
@supercat "o sistema não teria como identificar o que o Car.Drive virtual deve fazer." <- Ou poderia apenas dar um erro de compilação e fazer você escolher um explicitamente, como o C ++.
Chris Middleton
1
@ ChrisMiddleton: Um método void UseCar(Car &foo); não se pode esperar que inclua desambiguação entre ToyotaCar::Drivee HybridCar::Drive(já que muitas vezes nem deve saber nem se importar que esses outros tipos existam ). Uma linguagem pode, como o C ++, exigir que o código que ToyotaCar &myCardeseja transmiti-lo UseCarseja primeiro convertido para um HybridCarou ToyotaCar, mas desde ((Car) (HybridCar) myCar) .Drive` e ((Car)(ToyotaCar)myCar).Drive faria coisas diferentes, isso implicaria que os upcasts não preservavam a identidade.
Supercat
6

Você pode encontrar respostas precisas para esta consulta na página de documentação da Oracle sobre herança múltipla

  1. Herança múltipla de estado: capacidade de herdar campos de várias classes

    Uma razão pela qual a linguagem de programação Java não permite que você estenda mais de uma classe é evitar os problemas de herança múltipla de estado, que é a capacidade de herdar campos de várias classes

    Se herança múltipla for permitida e Quando você criar um objeto instanciando essa classe, esse objeto herdará campos de todas as superclasses da classe. Isso causará dois problemas.

    1. E se métodos ou construtores de superclasses diferentes instanciarem o mesmo campo?
    2. Qual método ou construtor terá precedência?
  2. Herança múltipla de implementação: capacidade de herdar definições de métodos de várias classes

    Problemas com essa abordagem: nome conflic ts e ambiguidade . Se uma subclasse e uma superclasse contiverem o mesmo nome de método (e assinatura), o compilador não poderá determinar qual versão chamar.

    Mas o java suporta esse tipo de herança múltipla com métodos padrão , que foram introduzidos desde o lançamento do Java 8. O compilador Java fornece algumas regras para determinar qual método padrão uma determinada classe usa.

    Consulte a publicação SE abaixo para obter mais detalhes sobre como resolver o problema do diamante:

    Quais são as diferenças entre classes abstratas e interfaces no Java 8?

  3. Herança múltipla do tipo: capacidade de uma classe implementar mais de uma interface.

    Como a interface não contém campos mutáveis, você não precisa se preocupar com problemas que resultam da herança múltipla de estado aqui.

Ravindra babu
fonte
4

Java suporta herança múltipla apenas através de interfaces. Uma classe pode implementar qualquer número de interfaces, mas pode estender apenas uma classe.

A herança múltipla não é suportada porque leva a um problema mortal de diamante. No entanto, ele pode ser resolvido, mas leva a um sistema complexo, de modo que a herança múltipla foi descartada pelos fundadores de Java.

Em um white paper intitulado “Java: uma visão geral”, de James Gosling, em fevereiro de 1995 ( link ), dá uma idéia de por que a herança múltipla não é suportada em Java.

De acordo com Gosling:

"O JAVA omite muitos recursos confusos e mal utilizados do C ++ que, em nossa experiência, trazem mais sofrimento do que benefícios. Isso consiste principalmente em sobrecarga do operador (embora exista sobrecarga de método), herança múltipla e coerções automáticas extensas".

Praveen Kumar
fonte
o link não está acessível. Por favor, verifique e atualize-o.
MashukKhan 22/06
3

Pelo mesmo motivo, o C # não permite herança múltipla, mas permite implementar várias interfaces.

A lição aprendida com C ++ com herança múltipla foi que ela levou a mais problemas do que valia a pena.

Uma interface é um contrato do que sua classe precisa implementar. Você não ganha nenhuma funcionalidade da interface. A herança permite herdar a funcionalidade de uma classe pai (e na herança múltipla, que pode ser extremamente confusa).

A permissão de várias interfaces permite que você use Design Patterns (como o Adapter) para resolver os mesmos tipos de problemas que você pode resolver usando herança múltipla, mas de uma maneira muito mais confiável e previsível.

Justin Niessner
fonte
10
O C # não possui herança múltipla precisamente porque o Java não permite. Foi projetado muito mais tarde que Java. O principal problema da herança múltipla, penso eu, era a maneira como as pessoas eram ensinadas a usá-la à esquerda e à direita. O conceito de que a delegação na maioria dos casos é uma alternativa muito melhor simplesmente não existia no início e meados dos anos 90. Por isso, lembro-me de exemplos, nos livros didáticos, quando Car é uma roda, uma porta e um para-brisa, versus Car contém rodas, portas e para-brisa. Portanto, a herança única em Java foi uma reação instintiva a essa realidade.
Alexander Pogrebnyak 25/03
2
@AlexanderPogrebnyak: Escolha dois dos três seguintes: (1) Permitir conversões de preservação de identidade de uma referência de subtipo para uma referência de supertipo; (2) Permitir que uma classe adicione membros públicos virtuais sem recompilar classes derivadas; (3) Permita que uma classe herde implicitamente membros virtuais de várias classes base sem precisar especificá-los explicitamente. Não acredito que seja possível para qualquer idioma gerenciar os três itens acima. Java optou por # 1 e # 2, e C # seguiu o exemplo. Acredito que o C ++ adote apenas o número 3. Pessoalmente, acho que os números 1 e 2 são mais úteis que o número 3, mas outros podem ser diferentes.
Supercat
@supercat "Não acredito que seja possível que qualquer idioma gerencie os três itens acima" - se as compensações dos membros dos dados forem determinadas em tempo de execução, em vez do tempo de compilação (como na ABI não frágil da Objective-C) ") e / ou por classe (ou seja, cada classe concreta tem sua própria tabela de deslocamento de membros), acho que todos os três objetivos podem ser alcançados.
O Croissant Paramagnético
@TheParamagneticCroissant: O principal problema semântico é o número 1. Se D1e D2ambos herdam de B, e cada um substitui uma função f, e se objé uma instância de um tipo Sque herda de ambos D1e D2mas não substitui f, a conversão de uma referência Sa D1deve produzir algo que fusa a D1substituição e a conversão de Bnão deve mude isso. Da mesma forma, converter uma referência Sem D2deve produzir algo cujo fuso D2substitua, e converter em Bnão deve mudar isso. Se um idioma não precisou permitir a adição de membros virtuais ...
supercat
1
@TheParamagneticCroissant: Poderia, e minha lista de opções foi simplificada demais para caber em um comentário, mas adiar para o tempo de execução torna impossível para o autor de D1, D2 ou S saber quais mudanças eles podem fazer sem quebrar consumidores de sua classe.
supercat
2

Como este tópico não está fechado, postarei esta resposta, espero que ajude alguém a entender por que o java não permite herança múltipla.

Considere a seguinte classe:

public class Abc{

    public void doSomething(){

    }

}

Nesse caso a classe Abc não estende nada né? Não tão rápido, essa classe implícita estende a classe Object, classe base que permite que tudo funcione em java. Tudo é um objeto.

Se você tentar usar a classe acima, você verá que o seu IDE permitem que você use métodos como: equals(Object o), toString(), etc, mas você não declarou esses métodos, eles vieram da classe baseObject

Você poderia tentar:

public class Abc extends String{

    public void doSomething(){

    }

}

Isso é bom, porque sua classe não será estendida implícita, Objectmas será estendida Stringporque você a disse. Considere a seguinte alteração:

public class Abc{

    public void doSomething(){

    }

    @Override
    public String toString(){
        return "hello";
    }

}

Agora sua classe sempre retornará "olá" se você chamar toString ().

Agora imagine a seguinte classe:

public class Flyer{

    public void makeFly(){

    }

}

public class Bird extends Abc, Flyer{

    public void doAnotherThing(){

    }

}

Novamente, a classe Flyerimplícita estende o Object que possui o método toString(), qualquer classe terá esse método, pois todos se estendem Objectindiretamente; portanto, se você chamar toString()de Bird, qual toString()java teria que usar? De Abcou Flyer? Isso acontecerá com qualquer classe que tentar estender duas ou mais classes. Para evitar esse tipo de "colisão de método", eles criaram a idéia de interface . Basicamente, você pode considerá-los uma classe abstrata que não estende Objeto indiretamente . Por serem abstratos , terão que ser implementados por uma classe, que é um objeto (você não pode instanciar uma interface sozinha, ela deve ser implementada por uma classe); portanto, tudo continuará funcionando bem.

Para diferenciar as classes das interfaces, a palavra-chave implementa foi reservada apenas para interfaces.

Você pode implementar qualquer interface que desejar na mesma classe, uma vez que ela não estende nada por padrão (mas você pode criar uma interface que estende outra interface, mas, novamente, a interface "pai" não estende Objeto "), portanto, uma interface é apenas uma interface e eles não sofrerão com " colisões de assinatura de métodos ", se o compilador enviar um aviso para você e você apenas precisará alterar a assinatura do método para corrigi-la (assinatura = nome do método + parâmetros + tipo de retorno) .

public interface Flyer{

    public void makeFly(); // <- method without implementation

}

public class Bird extends Abc implements Flyer{

    public void doAnotherThing(){

    }

    @Override
    public void makeFly(){ // <- implementation of Flyer interface

    }

    // Flyer does not have toString() method or any method from class Object, 
    // no method signature collision will happen here

}
Murillo Ferreira
fonte
1

Porque uma interface é apenas um contrato. E uma classe é realmente um contêiner para dados.

Serpente
fonte
A dificuldade fundamental da herança múltipla é a possibilidade de uma classe herdar um membro por meio de vários caminhos que a implementam de maneira diferente, sem fornecer sua própria implementação de substituição. A herança de interface evita isso porque o único local em que os membros da interface podem ser implementados é em classes, cujos descendentes serão limitados à herança única.
Supercat
1

Por exemplo, duas classes A, B com o mesmo método m1 (). E a classe C estende ambos A, B.

 class C extends A, B // for explaining purpose.

Agora, a classe C pesquisará a definição de m1. Primeiro, ele pesquisará na classe se não encontrar, depois verificará a classe dos pais. Ambos A, B com a definição Então aqui ocorre a ambiguidade que definição deve escolher. Então, o JAVA NÃO SUPORTA MÚLTIPLAS HERANÇA.

Pooja Khatri
fonte
que tal fazer o compilador Java dá compilador se os mesmos métodos ou variáveis definidas tanto em classe pai para que possamos alterar o código ...
siluveru Kiran Kumar
1

Java não suporta herança múltipla por dois motivos:

  1. Em java, toda classe é filha da Objectclasse. Quando herda de mais de uma superclasse, a subclasse obtém a ambiguidade de adquirir a propriedade da classe Object.
  2. Em java, toda classe tem um construtor, se a escrevermos explicitamente ou não. A primeira instrução está chamando super()para chamar o construtor da classe supper. Se a turma tiver mais de uma super turma, ela ficará confusa.

Portanto, quando uma classe se estende de mais de uma superclasse, obtemos um erro de tempo de compilação.

raghu.gb Raghu
fonte
0

Tomemos, por exemplo, o caso em que a Classe A possui um método getSomething e a classe B possui um método getSomething e a classe C estende A e B. O que aconteceria se alguém chamasse C.getSomething? Não há como determinar qual método chamar.

As interfaces basicamente apenas especificam quais métodos uma classe de implementação precisa conter. Uma classe que implementa várias interfaces significa apenas que a classe precisa implementar os métodos de todas essas interfaces. O Whci não levaria a nenhum problema, conforme descrito acima.

John Kane
fonte
2
" O que aconteceria se alguém chamasse C.getSomething. " É um erro no C ++. Problema resolvido.
Curiousguy
Esse era o ponto ... esse era um contra-exemplo que eu achava claro. Eu estava apontando que não há como determinar qual método de obtenção de algo deve ser chamado. Também como uma nota lateral, a questão estava relacionada com java não c ++
John Kane
Lamento não ter entendido qual é o seu ponto. Obviamente, há ambiguidade em alguns casos com IM. Como isso é um exemplo contrário? Quem alegou que o MI nunca resulta em ambiguidade? " Também como uma nota lateral, a questão estava relacionada a java não c ++ " Então?
Curiousguy 20/05
Eu só estava tentando mostrar por que essa ambiguidade existe e por que não faz com interfaces.
John Kane
Sim, o MI pode resultar em chamadas ambíguas. O mesmo pode sobrecarregar. Então, o Java deve remover a sobrecarga?
Curiousguy
0

Considere um cenário em que Teste1, Teste2 e Teste3 são três classes. A classe Test3 herda as classes Test2 e Test1. Se as classes Test1 e Test2 tiverem o mesmo método e você o chamar de objeto de classe filho, haverá ambiguidade para chamar o método da classe Test1 ou Test2, mas não haverá ambiguidade para a interface, pois na interface não há implementação.

Nikhil Kumar
fonte
0

Java não suporta herança múltipla, caminhos múltiplos e herança híbrida devido a um problema de ambiguidade:

 Scenario for multiple inheritance: Let us take class A , class B , class C. class A has alphabet(); method , class B has also alphabet(); method. Now class C extends A, B and we are creating object to the subclass i.e., class C , so  C ob = new C(); Then if you want call those methods ob.alphabet(); which class method takes ? is class A method or class B method ?  So in the JVM level ambiguity problem occurred. Thus Java does not support multiple inheritance.

herança múltipla

Link de referência: https://plus.google.com/u/0/communities/102217496457095083679


fonte
0

De maneira simples, como todos sabemos, podemos herdar (estender) uma classe, mas podemos implementar tantas interfaces. Isso ocorre porque nas interfaces não fornecemos uma implementação, apenas digamos a funcionalidade. suponha que java possa estender tantas classes e que tenham os mesmos métodos .. neste ponto, se tentarmos chamar o método super class na subclasse que método supõe executar ??, o compilador se confunde exemplo: - tente várias extensões, mas em interfaces esses métodos não têm corpos, devemos implementar aqueles na subclasse .. tente vários implementos para não se preocupar ..

hasanga lakdinu
fonte
1
Você pode apenas postar aqui esses exemplos. Sem isso, isso parece um bloco de texto para uma pergunta de 9 anos respondida centenas de vezes.
Pochmurnik 19/09/19
-1

* Esta é uma resposta simples, pois sou iniciante em Java *

Considere existem três classes X, Ye Z.

Então, estamos herdando como X extends Y, Z And both Ye estamos Ztendo um método alphabet()com o mesmo tipo de retorno e argumentos. Este método alphabet()no Ydiz para exibir o primeiro alfabeto e o método do alfabeto no Zdiz exibir o último alfabeto . Então aqui vem a ambiguidade quando alphabet()é chamado por X. Se diz para exibir o primeiro ou o último alfabeto ?? Portanto, o java não suporta herança múltipla. No caso de interfaces, considere Ye Zcomo interfaces. Portanto, ambos conterão a declaração do método, alphabet()mas não a definição. Ele não diz se deve exibir o primeiro alfabeto ou o último alfabeto ou qualquer outra coisa, mas apenas declarará um métodoalphabet(). Portanto, não há razão para aumentar a ambiguidade. Podemos definir o método com o que quisermos dentro da classe X.

Assim, em uma palavra, na definição de Interfaces é feita após a implementação, para que não haja confusão.

AJavaLover
fonte