¿Por qué no hay herencia múltiple en Java, pero se permite implementar múltiples interfaces?

153

Java no permite la herencia múltiple, pero permite implementar múltiples interfaces. ¿Por qué?

abson
fuente
1
Edité el título de la pregunta para hacerlo más descriptivo.
Bozho
44
Curiosamente, en el JDK 8, habrá métodos de extensión que permitirán la definición de implementación de métodos de interfaz. Las reglas se han definido para gobernar la herencia múltiple de comportamiento, pero no de estado (lo que entiendo es más problemático .)
Edwin Dalorzo
1
Antes de que pierdan el tiempo con respuestas que solo les dicen "Cómo Java logra la herencia múltiple" ... Les sugiero que vayan a la respuesta de @Prabal Srivastava que lógicamente les da lo que debe estar sucediendo internamente, para no permitir clases así. . y solo permite a las interfaces el derecho de permitir la herencia múltiple.
Yo Apps

Respuestas:

228

Debido a que las interfaces especifican solo lo que está haciendo la clase, no cómo lo está haciendo.

El problema con la herencia múltiple es que dos clases pueden definir diferentes formas de hacer lo mismo, y la subclase no puede elegir cuál elegir.

Bozho
fuente
8
Solía ​​hacer C ++ y me encontré con el mismo problema varias veces. Hace poco leí que Scala tiene "rasgos" que me parecen algo entre la forma "C ++" y la forma "Java" de hacer las cosas.
Niels Basjes
2
De esta manera, está evitando el "problema del diamante": en.wikipedia.org/wiki/Diamond_problem#The_diamond_problem
Nick L.
66
Desde Java 8 puede definir dos métodos predeterminados idénticos, uno en cada interfaz. Si va a implementar ambas interfaces en su clase, debe anular este método en la clase misma, consulte: docs.oracle.com/javase/tutorial/java/IandI/…
bobbel
66
Esta respuesta no es precisa. El problema no es especificar el cómo y Java 8 está ahí para probar. En Java 8, dos super interfaces pueden declarar el mismo método con diferentes implementaciones, pero eso no es un problema porque los métodos de interfaz son virtuales , por lo que puede sobrescribirlos y el problema se resuelve. El verdadero problema es la ambigüedad en los atributos , porque no puede resolver esta ambigüedad sobrescribiendo el atributo (los atributos no son virtuales).
Alex Oliveira
1
¿Qué quieres decir con "atributos"?
Bozho
96

Uno de mis instructores universitarios me lo explicó de esta manera:

Supongamos que tengo una clase, que es una tostadora, y otra clase, que es NuclearBomb. Ambos podrían tener un entorno de "oscuridad". Ambos tienen un método on (). (Uno tiene un off (), el otro no). Si quiero crear una clase que sea una subclase de ambos ... como puede ver, este es un problema que realmente podría explotar en mi cara aquí .

Entonces, uno de los problemas principales es que si tiene dos clases principales, pueden tener implementaciones diferentes de la misma característica, o posiblemente dos características diferentes con el mismo nombre, como en el ejemplo de mi instructor. Luego debe decidir cuál va a utilizar su subclase. Ciertamente, hay formas de manejar esto, C ++ lo hace, pero los diseñadores de Java sintieron que esto complicaría las cosas.

Sin embargo, con una interfaz, está describiendo algo que la clase es capaz de hacer, en lugar de tomar prestado el método de otra clase para hacer algo. Es mucho menos probable que varias interfaces causen conflictos difíciles que deben resolverse que las clases principales múltiples.

Sintáctico
fuente
55
Gracias NomeN. La fuente de la cita fue un estudiante de doctorado llamado Brendan Burns, quien en ese momento también era el mantenedor del repositorio de código abierto Quake 2. Imagínate.
Sintáctico
44
El problema con esta analogía es que si está creando una subclase de una bomba nuclear y una tostadora, la "tostadora nuclear" explotaría razonablemente cuando se usara.
101100
20
Si alguien decide mezclar una bomba nuclear y una tostadora, merece que la bomba explote en su cara. La cita es razonamiento falaz
masoud
1
El mismo lenguaje de programación permite la herencia de "interfaz" múltiple, por lo que esta "justificación" no se aplica.
curioso
24

Debido a que la herencia se usa en exceso incluso cuando no puedes decir "oye, ese método parece útil, también extenderé esa clase".

public class MyGodClass extends AppDomainObject, HttpServlet, MouseAdapter, 
             AbstractTableModel, AbstractListModel, AbstractList, AbstractMap, ...
Michael Borgwardt
fuente
¿Puede explicar por qué dice que la herencia se usa en exceso? ¡Crear una clase de dios es exactamente lo que quiero hacer! Encuentro a muchas personas que trabajan en torno a la herencia única creando clases de "Herramientas" que tienen métodos estáticos.
Duncan Calvert
9
@DuncanCalvert: No, no desea hacer eso, no si ese código alguna vez necesitará mantenimiento. Muchos métodos estáticos pierden el sentido de OO, pero la herencia múltiple excesiva es mucho peor porque se pierde completamente la noción de qué código se usa dónde y qué clase conceptual es. Ambos intentan resolver el problema de "cómo puedo usar este código donde lo necesito", pero ese es un problema simple a corto plazo. El problema a largo plazo mucho más difícil resuelto por un diseño OO adecuado es "¿cómo cambio este código sin que el programa se rompa en 20 lugares diferentes de maneras imprevisibles?"
Michael Borgwardt
2
@DuncanCalvert: y lo resuelve teniendo clases con alta cohesión y bajo acoplamiento, lo que significa que contienen piezas de datos y código que interactúan intensamente entre sí, pero que interactúan con el resto del programa solo a través de una API pública pequeña y simple. Luego puede pensar en ellos en términos de esa API en lugar de los detalles internos, lo cual es importante porque las personas solo pueden tener en cuenta una cantidad limitada de detalles al mismo tiempo.
Michael Borgwardt
18

La respuesta a esta pregunta radica en el funcionamiento interno del compilador de Java (encadenamiento del constructor). Si vemos el funcionamiento interno del compilador de Java:

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

Después de compilar este aspecto:

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

cuando ampliamos la clase y creamos un objeto de la misma, una cadena de constructores se ejecutará hasta la Objectclase.

El código anterior se ejecutará bien. pero si tenemos otra clase llamada Carque se extiende Banky una clase híbrida (herencia múltiple) llamada 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");
  }
}

En este caso (SBICar) no se podrá crear la cadena del constructor ( ambigüedad de tiempo de compilación ).

Para las interfaces esto está permitido porque no podemos crear un objeto de él.

Para conocer el nuevo concepto defaulty staticmétodo, consulte el valor predeterminado en la interfaz .

Espero que esto resuelva tu consulta. Gracias.

Prabal Srivastava
fuente
7

La implementación de múltiples interfaces es muy útil y no causa muchos problemas a los implementadores de lenguaje ni a los programadores. Entonces está permitido. La herencia múltiple, aunque también es útil, puede causar serios problemas a los usuarios (temido diamante de la muerte ). Y la mayoría de las cosas que haces con herencia múltiple también se pueden hacer por composición o usando clases internas. Por lo tanto, la herencia múltiple está prohibida ya que trae más problemas que ganancias.

Tadeusz Kopec
fuente
¿Cuál es el problema con el "diamante de la muerte"?
curioso
2
@curiousguy Contiene más de un subobjeto de la misma clase base, ambigüedad (anulación del uso de la clase base), reglas complicadas para resolver dicha ambigüedad.
Tadeusz Kopec
@curiousguy: si un marco proporciona que la conversión de una referencia de objeto a una referencia de tipo base preservará la identidad, entonces cada instancia de objeto debe tener exactamente una implementación de cualquier método de clase base. Si ToyotaCary HybridCarambos derivan Cary anulan Car.Drive, y si PriusCarheredan ambos pero no anulanDrive , el sistema no tendría forma de identificar lo que Car.Drivedebería hacer el virtual . Las interfaces evitan este problema al evitar la condición en cursiva anterior.
supercat
1
@supercat "el sistema no tendría forma de identificar lo que debería hacer el Car.Drive virtual". <- O simplemente podría dar un error de compilación y hacerte elegir uno explícitamente, como lo hace C ++.
Chris Middleton
1
@ChrisMiddleton: un método void UseCar(Car &foo); no se puede esperar que incluya desambiguación entre ToyotaCar::Drivey HybridCar::Drive(ya que a menudo no debe saber ni preocuparse de que esos otros tipos existan ). Un lenguaje podría, como lo hace C ++, requerir que ese código con el ToyotaCar &myCardeseo de pasarlo UseCarprimero deba emitirse a cualquiera de los dos HybridCaro ToyotaCar, pero desde ((Car) (HybridCar) myCar) .Drive` y ((Car)(ToyotaCar)myCar).Drive haría cosas diferentes, eso implicaría que los upcasts no preservaban la identidad.
supercat
6

Puede encontrar una respuesta precisa para esta consulta en la página de documentación de Oracle sobre herencia múltiple

  1. Herencia múltiple de estado: capacidad de heredar campos de múltiples clases

    Una razón por la cual el lenguaje de programación Java no le permite extender más de una clase es evitar los problemas de herencia múltiple de estado, que es la capacidad de heredar campos de múltiples clases

    Si se permite la herencia múltiple y cuando crea un objeto instanciando esa clase, ese objeto heredará campos de todas las superclases de la clase. Causará dos problemas.

    1. ¿Qué pasa si los métodos o constructores de diferentes superclases crean una instancia del mismo campo?
    2. ¿Qué método o constructor tendrá prioridad?
  2. Herencia múltiple de implementación: capacidad de heredar definiciones de métodos de múltiples clases

    Problemas con este enfoque: nombre de conflictos y ambigüedad . Si una subclase y una superclase contienen el mismo nombre de método (y firma), el compilador no puede determinar qué versión invocar.

    Pero Java admite este tipo de herencia múltiple con métodos predeterminados , que se han introducido desde el lanzamiento de Java 8. El compilador de Java proporciona algunas reglas para determinar qué método predeterminado usa una clase en particular.

    Consulte la publicación SE a continuación para obtener más detalles sobre la resolución del problema del diamante:

    ¿Cuáles son las diferencias entre las clases abstractas y las interfaces en Java 8?

  3. Herencia múltiple de tipo: capacidad de una clase para implementar más de una interfaz.

    Dado que la interfaz no contiene campos mutables, no tiene que preocuparse por los problemas que resultan de la herencia de estado múltiple aquí.

Ravindra babu
fuente
4

Java admite herencia múltiple solo a través de interfaces. Una clase puede implementar cualquier número de interfaces, pero solo puede extender una clase.

La herencia múltiple no es compatible porque conduce a un problema mortal de diamantes. Sin embargo, se puede resolver, pero conduce a un sistema complejo, por lo que los fundadores de Java han eliminado la herencia múltiple.

En un libro blanco titulado "Java: una descripción general" de James Gosling en febrero de 1995 ( enlace ) da una idea de por qué la herencia múltiple no es compatible con Java.

De acuerdo con Gosling:

"JAVA omite muchas características confusas y poco utilizadas de C ++ que, según nuestra experiencia, traen más dolor que beneficio. Esto consiste principalmente en la sobrecarga del operador (aunque sí tiene sobrecarga del método), herencia múltiple y extensas coacciones automáticas".

Praveen Kumar
fuente
El enlace no es accesible. Por favor, compruébalo y actualízalo.
MashukKhan
3

Por la misma razón, C # no permite la herencia múltiple, pero le permite implementar múltiples interfaces.

La lección aprendida de C ++ con herencia múltiple fue que conducía a más problemas de los que valía la pena.

Una interfaz es un contrato de cosas que su clase tiene que implementar. No obtienes ninguna funcionalidad de la interfaz. La herencia le permite heredar la funcionalidad de una clase principal (y en herencia múltiple, eso puede ser extremadamente confuso).

Permitir múltiples interfaces le permite usar Patrones de diseño (como Adaptador) para resolver los mismos tipos de problemas que puede resolver usando la herencia múltiple, pero de una manera mucho más confiable y predecible.

Justin Niessner
fuente
10
C # no tiene herencia múltiple precisamente porque Java no lo permite. Fue diseñado mucho más tarde que Java. El principal problema con la herencia múltiple, creo, fue la forma en que se enseñó a las personas a usarlo de izquierda a derecha. El concepto de que la delegación en la mayoría de los casos es una alternativa mucho mejor simplemente no existía a principios y mediados de los noventa. Por lo tanto, recuerdo ejemplos, en los libros de texto, cuando el automóvil es una rueda y una puerta y un parabrisas, en comparación con el automóvil contiene ruedas, puertas y parabrisas. Entonces, la herencia única en Java fue una reacción instintiva a esa realidad.
Alexander Pogrebnyak
2
@AlexanderPogrebnyak: Elija dos de los siguientes tres: (1) Permitir conversiones de preservación de identidad de una referencia de subtipo a una referencia de supertipo; (2) Permitir que una clase agregue miembros públicos virtuales sin volver a compilar clases derivadas; (3) Permitir que una clase herede implícitamente miembros virtuales de múltiples clases base sin tener que especificarlos explícitamente. No creo que ningún idioma pueda gestionar los tres anteriores. Java optó por # 1 y # 2, y C # hizo lo mismo. Creo que C ++ adopta solo el n. ° 3. Personalmente, creo que # 1 y # 2 son más útiles que # 3, pero otros pueden diferir.
supercat
@supercat "No creo que sea posible que ningún lenguaje administre los tres anteriores", si las compensaciones de los miembros de datos se determinan en tiempo de ejecución, en lugar de tiempo de compilación (como lo están en la ABI no frágil de Objective-C "), y / o por clase (es decir, cada clase concreta tiene su propia tabla de compensación de miembros), entonces creo que se pueden lograr los 3 objetivos.
El cruasán paramagnético
@TheParamagneticCroissant: El principal problema semántico es el # 1. Si D1y D2ambos heredan de B, y cada uno anula una función f, y si objes una instancia de un tipo Sque hereda de ambos D1y D2no anula f, entonces emitir una referencia Sa D1debería producir algo cuyo fuso D1anule, y emitir a Bno debería cambia eso. Del mismo modo, emitir una referencia Sa D2debería producir algo que fusa la D2anulación, y emitir a Bno debería cambiar eso. Si un idioma no necesita permitir que se agreguen miembros virtuales ...
supercat
1
@TheParamagneticCroissant: podría, y mi lista de opciones se simplificó demasiado para que quepa en un comentario, pero diferir el tiempo de ejecución hace que sea imposible para el autor de D1, D2 o S saber qué cambios pueden hacer sin romper consumidores de su clase.
supercat
2

Como este tema no está cerrado, publicaré esta respuesta, espero que esto ayude a alguien a entender por qué Java no permite la herencia múltiple.

Considere la siguiente clase:

public class Abc{

    public void doSomething(){

    }

}

En este caso, la clase Abc no extiende nada ¿verdad? No tan rápido, esta clase implícita extiende la clase Object, clase base que permite que todo funcione en Java. Todo es un objeto.

Si intenta utilizar la clase anterior se verá que su IDE permite utilizar métodos como: equals(Object o), toString(), etc, pero no declaró esos métodos, vinieron de la clase baseObject

Tu podrías intentar:

public class Abc extends String{

    public void doSomething(){

    }

}

Esto está bien, porque su clase no se extenderá implícitamente, Objectsino que se extenderá Stringporque usted lo dijo. Considere el siguiente cambio:

public class Abc{

    public void doSomething(){

    }

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

}

Ahora su clase siempre devolverá "hola" si llama a ToString ().

Ahora imagine la siguiente clase:

public class Flyer{

    public void makeFly(){

    }

}

public class Bird extends Abc, Flyer{

    public void doAnotherThing(){

    }

}

Nuevamente, la clase Flyerimplícita extiende el Objeto que tiene el método toString(), cualquier clase tendrá este método ya que todos se extienden Objectindirectamente, entonces, si llama toString()desde Bird, ¿qué toString()Java tendría que usar? ¿De Abco Flyer? Esto sucederá con cualquier clase que intente extender dos o más clases, para evitar este tipo de "colisión de métodos" construyeron la idea de interfaz , básicamente podría pensar que son una clase abstracta que no extiende el Objeto indirectamente . Dado que son abstractos , deberán ser implementados por una clase, que es un objeto(no se puede instanciar una interfaz solo, debe ser implementado por una clase), por lo que todo continuará funcionando bien.

Para diferenciar las clases de las interfaces, la palabra clave implements estaba reservada solo para interfaces.

Puede implementar cualquier interfaz que desee en la misma clase, ya que no extiende nada de forma predeterminada (pero puede crear una interfaz que extienda otra interfaz, pero nuevamente, la interfaz "padre" no extiende Objeto "), por lo que una interfaz es solo una interfaz y no sufrirán " colisiones de firma de métodos ", si lo hacen, el compilador le enviará una advertencia y solo tendrá que cambiar la firma del método para solucionarlo (firma = nombre del 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
fuente
1

Porque una interfaz es solo un contrato. Y una clase es en realidad un contenedor de datos.

Serpiente
fuente
La dificultad fundamental con la herencia múltiple es la posibilidad de que una clase pueda heredar un miembro a través de múltiples rutas que lo implementan de manera diferente, sin proporcionar su propia implementación principal. La herencia de la interfaz evita esto porque el único lugar donde se pueden implementar los miembros de la interfaz es en clases, cuyos descendientes se limitarán a la herencia única.
supercat
1

Por ejemplo, dos clases A, B con el mismo método m1 (). Y la clase C extiende tanto a A como a B.

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

Ahora, la clase C buscará la definición de m1. Primero, buscará en la clase si no encontró, luego verificará en la clase de los padres. Ambos A, B tienen la definición Entonces, aquí se produce la ambigüedad, qué definición debería elegir. Entonces JAVA NO APOYA LA HERENCIA MÚLTIPLE.

Pooja Khatri
fuente
¿Qué tal hacer el compilador Java da compilador si los mismos métodos o variables definidas tanto en la clase padre para que podamos cambiar el código ...
siluveru Kiran Kumar
1

Java no admite herencia múltiple por dos razones:

  1. En Java, cada clase es un hijo de Objectclase. Cuando hereda de más de una superclase, la subclase obtiene la ambigüedad de adquirir la propiedad de la clase Object.
  2. En Java, cada clase tiene un constructor, si lo escribimos explícitamente o no lo hacemos. La primera instrucción llama super()a invocar el constructor de la clase supper. Si la clase tiene más de una superclase, se confunde.

Entonces, cuando una clase se extiende desde más de una superclase, obtenemos un error de tiempo de compilación.

raghu.gb Raghu
fuente
0

Tomemos, por ejemplo, el caso en que la Clase A tiene un método getSomething y la clase B tiene un método getSomething y la clase C extiende A y B. ¿Qué pasaría si alguien llamara C.getSomething? No hay forma de determinar a qué método llamar.

Las interfaces básicamente solo especifican qué métodos debe contener una clase de implementación. Una clase que implementa múltiples interfaces solo significa que esa clase tiene que implementar los métodos de todas esas interfaces. Whci no conduciría a ningún problema como se describió anteriormente.

John Kane
fuente
2
" ¿Qué pasaría si alguien llamara C.getSomething? " Es un error en C ++. Problema resuelto.
curioso
Ese fue el punto ... ese fue un contraejemplo que pensé que estaba claro. Estaba señalando que no hay forma de determinar qué método get algo debería llamarse. También como nota al margen, la pregunta estaba relacionada con Java, no c ++
John Kane
Lo siento, no entiendo cuál es tu punto. Obviamente, hay ambigüedad en algunos casos con IM. ¿Cómo es eso un contraejemplo? ¿Quién afirmó que MI nunca da lugar a ambigüedad? " También como nota al margen, la pregunta estaba relacionada con Java, no con C ++ " ¿Entonces?
curioso
Solo intentaba mostrar por qué existe esa ambigüedad y por qué no funciona con las interfaces.
John Kane
Sí, MI puede resultar en llamadas ambiguas. Entonces puede sobrecargar. Entonces, ¿Java debería eliminar la sobrecarga?
curioso
0

Considere un escenario donde Test1, Test2 y Test3 son tres clases. La clase Test3 hereda las clases Test2 y Test1. Si las clases Test1 y Test2 tienen el mismo método y lo llama desde un objeto de clase hijo, habrá ambigüedad para llamar al método de la clase Test1 o Test2, pero no existe tal ambigüedad para la interfaz, ya que en la interfaz no hay implementación.

Nikhil Kumar
fuente
0

Java no admite herencia múltiple, trayectoria múltiple e herencia híbrida debido a un problema de ambigüedad:

 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.

herencia múltiple

Enlace de referencia: https://plus.google.com/u/0/communities/102217496457095083679


fuente
0

De una manera simple que todos sabemos, podemos heredar (extender) una clase pero podemos implementar tantas interfaces ... eso es porque en las interfaces no damos una implementación, solo decimos la funcionalidad. supongo que si Java se extiende tantas clases y los que tienen mismos métodos .. en este punto si tratamos de invocar el método superclase de la clase sub qué método supone que debe funcionar ??, compilador se confunda ejemplo: - tratar de múltiples extiende pero en las interfaces de esos métodos no tienen cuerpos, debemos implementarlos en una subclase ... intente con varios implementos para que no se preocupe ...

Hasanga Lakdinu
fuente
1
Podrías publicar aquí esos ejemplos. Sin eso, esto parece un bloque de texto a la pregunta de 9 años respondida cientos de veces.
Pochmurnik
-1

* Esta es una respuesta simple ya que soy un principiante en Java *

Considere que hay tres clases X, Yy Z.

Entonces estamos heredando like X extends Y, Z And both Yy Zestá teniendo un método alphabet()con el mismo tipo de retorno y argumentos. Este método alphabet()en Ydice para mostrar el primer alfabeto y el alfabeto de método en Zdice mostrar el último alfabeto . Entonces aquí viene la ambigüedad cuando alphabet()se llama por X. ¿Si dice mostrar el primer alfabeto o el último ? Entonces, Java no es compatible con la herencia múltiple. En el caso de las interfaces, considere Yy Zcomo interfaces. Por lo tanto, ambos contendrán la declaración del método alphabet()pero no la definición. No indicará si se debe mostrar el primer alfabeto o el último alfabeto o cualquier otra cosa, pero solo declarará un métodoalphabet(). Entonces no hay razón para aumentar la ambigüedad. Podemos definir el método con todo lo que queramos dentro de la clase X.

En una palabra, en la definición de Interfaces se realiza después de la implementación, por lo que no hay confusión.

AJavaLover
fuente