Por que a herança múltipla não é permitida em Java ou C #?

115

Eu sei que herança múltipla não é permitida em Java e C #. Muitos livros apenas dizem que a herança múltipla não é permitida. Mas pode ser implementado usando interfaces. Nada é discutido sobre por que não é permitido. Alguém pode me dizer exatamente por que não é permitido?

Abdulsattar Mohammed
fonte
1
Assim como para salientar que não são quadros lá fora, que permitem MI-como o comportamento em classes C #. E é claro que você tem a opção de ter mixins sem estado (usando métodos de extensão) - embora sem estado, eles não são muito úteis.
Dmitri Nesteruk de
1
O fato de as interfaces terem algo a ver com herança é uma ilusão criada pela sintaxe da linguagem. A herança deveria torná-lo mais rico. Nada parecido com uma interface, você não herda o agachamento e isso o torna significativamente mais pobre. Você herdou a dívida de jogo do tio Harry, você tem mais trabalho a fazer para implementar a interface.
Hans Passant

Respostas:

143

A resposta curta é: porque os designers da linguagem decidiram não fazê-lo.

Basicamente, parecia que os designers .NET e Java não permitiam herança múltipla porque eles argumentaram que adicionar MI acrescentava muita complexidade às linguagens, ao mesmo tempo que proporcionava muito pouco benefício .

Para uma leitura mais divertida e aprofundada, existem alguns artigos disponíveis na web com entrevistas de alguns dos designers da linguagem. Por exemplo, para .NET, Chris Brumme (que trabalhou na MS no CLR) explicou as razões pelas quais eles decidiram não:

  1. Línguas diferentes, na verdade, têm expectativas diferentes sobre como o MI funciona. Por exemplo, como os conflitos são resolvidos e se as bases duplicadas são mescladas ou redundantes. Antes de podermos implementar MI no CLR, temos que fazer um levantamento de todas as linguagens, descobrir os conceitos comuns e decidir como expressá-los de uma maneira neutra em relação à linguagem. Também teríamos que decidir se MI pertence ao CLS e o que isso significaria para linguagens que não querem esse conceito (presumivelmente VB.NET, por exemplo). Claro, esse é o nosso negócio como um runtime de linguagem comum, mas ainda não temos tempo para fazer isso para MI.

  2. O número de lugares onde o MI é realmente apropriado é, na verdade, muito pequeno. Em muitos casos, a herança de várias interfaces pode fazer o trabalho. Em outros casos, você pode usar o encapsulamento e a delegação. Se adicionássemos uma construção ligeiramente diferente, como mixins, isso seria realmente mais poderoso?

  3. A herança de várias implementações injeta muita complexidade na implementação. Essa complexidade afeta o elenco, o layout, o despacho, o acesso ao campo, a serialização, as comparações de identidade, a verificabilidade, a reflexão, os genéricos e provavelmente muitos outros lugares.

Você pode ler o artigo completo aqui.

Para Java, você pode ler este artigo :

Os motivos para omitir a herança múltipla da linguagem Java derivam principalmente do objetivo "simples, orientado a objetos e familiar". Como uma linguagem simples, os criadores do Java queriam uma linguagem que a maioria dos desenvolvedores pudesse entender sem um treinamento extensivo. Para tanto, eles trabalharam para tornar a linguagem o mais semelhante possível ao C ++ (familiar) sem carregar a complexidade desnecessária do C ++ (simples).

Na opinião dos designers, a herança múltipla causa mais problemas e confusão do que resolve. Portanto, eles eliminam a herança múltipla da linguagem (assim como eliminam a sobrecarga do operador). A extensa experiência em C ++ dos designers ensinou-lhes que a herança múltipla simplesmente não valia a pena a dor de cabeça.

Razzie
fonte
10
Bela comparação. Bastante indicativo dos processos de pensamento que fundamentam ambas as plataformas, eu acho.
CurtainDog de
97

A herança múltipla de implementação é o que não é permitido.

O problema é que o compilador / tempo de execução não consegue descobrir o que fazer se você tiver uma classe Cowboy e uma classe Artist, ambas com implementações para o método draw (), e então tentar criar um novo tipo CowboyArtist. O que acontece quando você chama o método draw ()? Alguém está morto na rua ou você tem uma linda aquarela?

Eu acredito que é chamado de problema de herança de diamante duplo.

duffymo
fonte
80
Você obtém uma bela aquarela de alguém morto na rua :-)
Dan F
Este é o único problema? Acho, não tenho certeza, que o C ++ resolve esse problema pela palavra-chave virtual, isso é verdade? Não sou bom em C ++
Abdulsattar Mohammed
2
Em C ++, você pode especificar quais funções da classe base chamar derivada ou reimplementá-la você mesmo.
Dmitry Risenberg
1
O design moderno tende a favorecer a composição em vez da herança, exceto nos casos mais simples. A herança múltipla nunca seria considerada um caso simples. Com a composição, você tem controle preciso sobre o que sua classe faz quando há problemas com diamantes como este ...
Bill Michell
Você simplesmente precisa de uma lista de prioridades. Isso é usado no sistema de objetos do Common Lisp, CLOS. Todas as classes formam uma herança e o método que é chamado é determinado a partir daí. Além disso, CLOS permite que você defina regras diferentes, de modo que um CowboyArtist.draw () possa desenhar primeiro um Cowboy e depois um Artista. Ou o que quer que você invente.
Sebastian Krog
19

Motivo: Java é muito popular e fácil de codificar, devido à sua simplicidade.

Portanto, o que quer que os desenvolvedores java pareçam difíceis e complicados de entender para os programadores, eles tentam evitar. Um desses tipos de propriedade é a herança múltipla.

  1. Eles evitaram dicas
  2. Eles evitaram a herança múltipla.

Problema com herança múltipla: problema do diamante.

Exemplo :

  1. Suponha que a classe A esteja tendo um método fun (). a classe B e a classe C derivam da classe A.
  2. E ambas as classes B e C substituem o método fun ().
  3. Agora suponha que a classe D herde ambas as classes B e C. (apenas suposição)
  4. Crie um objeto para a classe D.
  5. D d = novo D ();
  6. e tente acessar d.fun (); => vai chamar fun () da classe B ou fun () da classe C?

Esta é a ambigüidade existente no problema do diamante.

Não é impossível resolver este problema, mas cria mais confusão e complexidades para o programador durante a leitura. Isso causa mais problemas do que tenta resolver.

Nota : Mas de qualquer maneira, você sempre pode implementar herança múltipla indiretamente usando interfaces.

user1923551
fonte
1
"Mas de qualquer forma, você sempre pode implementar herança múltipla indiretamente usando interfaces." - que requer que o programador especifique novamente o corpo dos métodos todas as vezes.
Kari
13

Porque Java tem uma filosofia de design muito diferente do C ++. (Não vou discutir C # aqui.)

Ao projetar C ++, Stroustrup queria incluir recursos úteis, independentemente de como eles poderiam ser mal utilizados. É possível estragar muito com herança múltipla, sobrecarga de operador, modelos e vários outros recursos, mas também é possível fazer algumas coisas muito boas com eles.

A filosofia de design Java é enfatizar a segurança nas construções de linguagem. O resultado é que há coisas que são muito mais difíceis de fazer, mas você pode ter muito mais certeza de que o código que está olhando significa o que você pensa que significa.

Além disso, Java foi em grande parte uma reação de C ++ e Smalltalk, as linguagens OO mais conhecidas. Existem muitas outras linguagens OO (Common Lisp foi na verdade a primeira a ser padronizada), com diferentes sistemas OO que lidam melhor com MI.

Sem falar que é inteiramente possível fazer MI em Java, usando interfaces, composição e delegação. É mais explícito do que em C ++ e, portanto, é mais complicado de usar, mas fornecerá algo que você provavelmente entenderá à primeira vista.

Não há uma resposta certa aqui. As respostas são diferentes, e qual é a melhor para uma determinada situação depende das aplicações e da preferência individual.

David Thornley
fonte
12

A principal (embora não seja a única) razão pela qual as pessoas se afastam do MI é o chamado "problema do diamante", que leva à ambiguidade em sua implementação. Este artigo da Wikipedia discute e explica melhor do que eu poderia. O MI também pode levar a um código mais complexo, e muitos designers OO afirmam que você não precisa do MI e, se usá-lo, seu modelo provavelmente está errado. Não tenho certeza se concordo com este último ponto, mas manter as coisas simples é sempre um bom plano.

Steve
fonte
8

Em C ++, a herança múltipla era uma grande dor de cabeça quando usada incorretamente. Para evitar esses problemas de design populares, a "herança" de várias interfaces foi forçada em linguagens modernas (java, C #).

inazaruk
fonte
8

A herança múltipla é

  • dificíl de entender
  • difícil de depurar (por exemplo, se você misturar classes de vários frameworks que têm métodos com nomes idênticos no fundo, podem ocorrer sinergias bastante inesperadas)
  • fácil de usar mal
  • não é realmente tão útil
  • difícil de implementar, especialmente se você quiser que seja feito de maneira correta e eficiente

Portanto, pode ser considerado uma escolha sábia não incluir a herança múltipla na linguagem Java.

mfx
fonte
3
Não concordo com todos os itens acima, tendo trabalhado com o Common Lisp Object System.
David Thornley
2
Linguagens dinâmicas não contam ;-) Em qualquer sistema do tipo Lisp, você tem um REPL que torna a depuração bastante fácil. Além disso, CLOS (pelo que entendi, nunca realmente usei, apenas li sobre ele) é um sistema de meta-objeto, com muita flexibilidade e uma atitude de roll-your-own. Mas considere uma linguagem compilada estaticamente como C ++, onde o compilador gera alguma pesquisa de método diabolicamente complexa usando várias tabelas v (possivelmente sobrepostas): em tal implementação, descobrir qual implementação de um método foi chamada pode ser uma tarefa não tão trivial.
mfx
5

Outra razão é que a herança simples torna a conversão trivial, não emitindo instruções do assembler (além de verificar a compatibilidade dos tipos quando necessário). Se você tivesse herança múltipla, precisaria descobrir onde na classe filha um determinado pai começa. Portanto, o desempenho é certamente uma vantagem (embora não seja a única).

Cego
fonte
4

Nos velhos tempos (anos 70), quando a Ciência da Computação era mais Ciência e menos produção em massa, os programadores tinham tempo para pensar em um bom design e boa implementação e, como resultado, os produtos (programas) tinham alta qualidade (por exemplo, design TCP / IP e implementação). Hoje em dia, quando todo mundo está programando e os gerentes estão mudando as especificações antes dos prazos, questões sutis como a descrita no link da Wikipedia da postagem de Steve Haigh são difíceis de rastrear; portanto, a "herança múltipla" é limitada pelo design do compilador. Se gostar, você ainda pode usar C ++ .... e ter toda a liberdade que quiser :)


fonte
4
... incluindo a liberdade de atirar no próprio pé, várias vezes;)
Mario Ortegón
4

Aceito a declaração de que "herança múltipla não é permitida em Java" com uma pitada de sal.

A herança múltipla é definida quando um "Tipo" herda de mais de um "Tipos". E as interfaces também são classificadas como tipos, pois têm comportamento. Portanto, Java tem herança múltipla. Só que é mais seguro.

Rig Veda
fonte
6
Mas você não herda de uma interface, você a implementa. Interfaces não são classes.
Blorgbeard será lançado em
2
Sim, mas herança nunca foi definida de forma tão restrita, exceto em alguns livros preliminares. E acho que devemos olhar além da gramática disso. Ambos fazem a mesma coisa, e as interfaces foram "inventadas" apenas por esse motivo.
Rig Veda de
Pegue a interface Closeable: um método, close (). Suponha que você queira estender isso para Openable: ele tem um método open () que define um campo booleano isOpen como true. Tentar abrir um Openable que já está aberto gera uma exceção, assim como tentar fechar um Openable que não está aberto ... Centenas de genealogias mais interessantes de classes podem querer adotar tal funcionalidade ... sem ter que escolher estender cada uma tempo da aula Openable. Se Openable fosse simplesmente uma interface, não poderia fornecer essa funcionalidade. QED: interfaces não fornecem herança!
roedor de microfone
4

O carregamento dinâmico de classes torna a implementação de herança múltipla difícil.

Na verdade, em java, eles evitavam a complexidade da herança múltipla, em vez de usar herança e interface simples. A complexidade da herança múltipla é muito alta em uma situação como explicada abaixo

problema de diamante de herança múltipla. Temos duas classes B e C herdando de A. Suponha que B e C estão substituindo um método herdado e fornecem sua própria implementação. Agora D herda de B e C fazendo herança múltipla. D deve herdar esse método sobrescrito, jvm não consegue decidir qual método sobrescrito será usado?

Em c ++ funções virtuais são usadas para manipular e temos que fazer explicitamente.

Isso pode ser evitado usando interfaces, não há corpos de método. As interfaces não podem ser instanciadas - elas só podem ser implementadas por classes ou estendidas por outras interfaces.

Sujith s
fonte
3

Na verdade, herança múltipla surgirá com a complexidade se as classes herdadas tiverem a mesma função. isto é, o compilador terá uma confusão sobre qual escolher (problema do diamante). Portanto, em Java essa complexidade foi removida e deu interface para obter a funcionalidade como herança múltipla fornecida. Podemos usar interface

Jinu
fonte
2

Java tem conceito, ou seja, polimorfismo. Existem 2 tipos de polimorfismo em java. Há sobrecarga de método e substituição de método. Entre eles, a substituição de métodos acontece com relacionamento de super e subclasse. Se estivermos criando um objeto de uma subclasse e invocando o método da superclasse, e se a subclasse estender mais de uma classe, qual método de superclasse deve ser chamado?

Ou, ao chamar o construtor da superclasse por super(), qual construtor da superclasse será chamado?

Essas decisões são impossíveis pelos recursos atuais da API Java. portanto, herança múltipla não é permitida em java.

Abhay Kokate
fonte
Fundamentalmente, o sistema de tipo de Java assume que cada instância de objeto tem um tipo, converter um objeto para um supertipo sempre funcionará e preservará a referência, e converter uma referência para um subtipo será preservador de referência se a instância for desse subtipo ou seu subtipo. Essas suposições são úteis e não acho que seja possível permitir a herança múltipla generalizada de uma forma consistente com elas.
supercat
1

A herança múltipla não é permitida em Java diretamente, mas por meio de interfaces é permitida.

Razão:

Herança múltipla: apresenta mais complexidade e ambiguidade.

Interfaces: interfaces são classes completamente abstratas em Java que fornecem uma maneira uniforme de delinear adequadamente a estrutura ou o funcionamento interno de seu programa a partir de sua interface disponível publicamente, com a consequência sendo uma maior quantidade de flexibilidade e código reutilizável, bem como mais controle sobre como você cria e interage com outras classes.

Mais precisamente, eles são uma construção especial em Java com a característica adicional que permite que você execute um tipo de herança múltipla, ou seja, classes que podem ser atualizadas para mais de uma classe.

Vamos dar um exemplo simples.

  1. Suponha que existam 2 superclasses de classes A e B com os mesmos nomes de método, mas com funcionalidades diferentes. Por meio do seguinte código com (estende) a herança múltipla de palavra-chave não é possível.

       public class A                               
         {
           void display()
             {
               System.out.println("Hello 'A' ");
             }
         }
    
       public class B                               
          {
            void display()
              {
                System.out.println("Hello 'B' ");
              }
          }
    
      public class C extends A, B    // which is not possible in java
        {
          public static void main(String args[])
            {
              C object = new C();
              object.display();  // Here there is confusion,which display() to call, method from A class or B class
            }
        }
  2. Mas por meio de interfaces, com (implementa) a herança múltipla de palavras-chave é possível.

    interface A
        {
           // display()
        }
    
    
     interface B
        {
          //display()
        }
    
     class C implements A,B
        {
           //main()
           C object = new C();
           (A)object.display();     // call A's display
    
           (B)object.display(); //call B's display
        }
    }
Chaitra Deshpande
fonte
0

Alguém pode me dizer exatamente por que não é permitido?

Você pode encontrar a resposta neste link de documentação

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 a 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?

Mesmo que a herança múltipla de estado agora seja permitida, você ainda pode implementar

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

Herança múltipla de implementação (por meio de métodos padrão em interfaces): Capacidade de herdar definições de método de várias classes

Consulte esta pergunta SE relacionada para obter informações adicionais:

Ambiguidade de herança múltipla com interface

Ravindra babu
fonte
0

Em C ++, uma classe pode herdar (direta ou indiretamente) de mais de uma classe, o que é conhecido como herança múltipla .

C # e Java, entretanto, limitam as classes à herança única que cada classe herda de uma única classe pai.

A herança múltipla é uma maneira útil de criar classes que combinam aspectos de duas hierarquias de classes distintas, algo que geralmente acontece ao usar diferentes estruturas de classe em um único aplicativo.

Se duas estruturas definem suas próprias classes de base para exceções, por exemplo, você pode usar herança múltipla para criar classes de exceção que podem ser usadas com qualquer estrutura.

O problema com a herança múltipla é que ela pode levar à ambiguidade. O exemplo clássico é quando uma classe herda de duas outras classes, cada uma das quais herda da mesma classe:

class A {
    protected:
    bool flag;
};
class B : public A {};
class C : public A {};
class D : public B, public C {
    public:
    void setFlag( bool nflag ){
        flag = nflag; // ambiguous
    }
};

Neste exemplo, o flagmembro de dados é definido por class A. Mas class Ddescende de class B e class C, de que ambos derivam A, então, em essência, duas cópias de flagestão disponíveis porque duas instâncias de Aestão na Dhierarquia de classes de. Qual você deseja definir? O compilador reclamará que a referência a flagin Dé ambígua . Uma correção é eliminar a ambiguidade da referência explicitamente:

B::flag = nflag;

Outra correção é declarar B e C como virtual base classes , o que significa que apenas uma cópia de A pode existir na hierarquia, eliminando qualquer ambigüidade.

Outras complexidades existem com herança múltipla, como a ordem em que as classes básicas são inicializadas quando um objeto derivado é construído ou a maneira como os membros podem ser inadvertidamente ocultados das classes derivadas. Para evitar essas complexidades, algumas linguagens se restringem ao modelo mais simples de herança única.

Embora isso simplifique consideravelmente a herança, também limita sua utilidade porque apenas classes com um ancestral comum podem compartilhar comportamentos. As interfaces atenuam um pouco essa restrição, permitindo que classes em diferentes hierarquias exponham interfaces comuns, mesmo que não sejam implementadas por meio do compartilhamento de código.

zerocool
fonte
-1

Imagine este exemplo: eu tenho uma aula Shape1

Tem CalcualteAreamétodo:

Class Shape1
{

 public void CalculateArea()

     {
       //
     }
}

Há outra classe Shape2que também tem o mesmo método

Class Shape2
{

 public void CalculateArea()

     {

     }
}

Agora eu tenho uma classe filha Circle, ela deriva de Shape1 e Shape2;

public class Circle: Shape1, Shape2
{
}

Agora, quando eu crio um objeto para Círculo e chamo o método, o sistema não sabe qual método de cálculo de área deve ser chamado. Ambos têm as mesmas assinaturas. Portanto, o compilador ficará confuso. É por isso que várias heranças não são permitidas.

Mas pode haver várias interfaces porque as interfaces não têm definição de método. Mesmo que ambas as interfaces tenham o mesmo método, ambas não possuem nenhuma implementação e sempre o método da classe filha será executado.

Engr Muhammad Enayet Abdullah
fonte
o designer de linguagem pode dar uma chamada de método explícita como eles fazem em Interface. Não existe tal ponto! Outra razão é que tal classe abstrata com métodos abstratos agora, como você considera isso?
Nomi Ali,