resueltos poo multiple interfaces herencia ejercicios ejemplos ejemplo clases clase abstractas abstracta java multiple-inheritance diamond-problem

poo - ¿Por qué se utiliza el caso de diamante con su ancestro común para explicar el problema de herencia múltiple de Java, en lugar de dos clases primarias no relacionadas?



herencia multiple en java ejemplos (6)

El problema con la herencia de diamante no es tanto el comportamiento compartido como el estado compartido . Como puede ver, de hecho, Java siempre ha admitido herencia múltiple, pero solo herencia de tipo múltiple .

Con solo tres clases, el problema se resuelve de manera relativamente fácil mediante la introducción de una construcción simple como super.A o super.B . Y aunque solo está buscando métodos anulados, de hecho no importa si tiene un ancestro común o solo las tres clases básicas.

Sin embargo, si A y B tienen un ancestro común, el estado del cual ambos heredan, entonces usted está en serios problemas. ¿Guardas dos copias separadas del estado de este ancestro común? Eso sería más una composición que una herencia. ¿O solo almacena uno compartido tanto por A como por B , lo que provoca interacciones extrañas cuando manipulan su estado compartido heredado?

class A { protected int foo; } class B extends A { public B() { this.foo = 42; } } class C extends A { public C() { this.foo = 0xf00; } } class D extends B,C { public D() { System.out.println( "Foo is: "+foo ); //Now what? } }

Tenga en cuenta que lo anterior no sería un problema tan grande si la clase A no existiera y B y C declararan su propio campo foo . Todavía habría un problema de nombres en conflicto, pero eso podría resolverse con alguna construcción de espacios de nombres ( B.this.foo y C.this.foo , tal vez, como hacemos con las clases internas?). Por otra parte, el verdadero problema de los diamantes es más que un choque de nombres, es una cuestión de cómo mantener invariantes de clase cuando dos superclases no relacionadas de D ( B y C ) comparten el mismo estado que ambos heredan de A Esta es la razón por la que se necesitan las cuatro clases para demostrar el alcance total del problema.

El comportamiento compartido en herencia múltiple no presenta el mismo problema. Tanto es así que los métodos predeterminados recientemente introducidos hacen exactamente eso. Esto significa que ahora también se permite la herencia múltiple de implementaciones. Todavía hay alguna complicación en torno a la resolución de a qué implementación llamar, pero como las interfaces no tienen estado, se evita el mayor error.

Esta pregunta puede sonar extraña para la gente de Java, pero si intentas explicar esto, sería genial.

En estos días estoy despejando algunos de los conceptos muy básicos de Java. Así que llego al tema de herencia e interfaz de Java.

Mientras leía esto, descubrí que Java no admite la herencia múltiple y también entendí que, lo que no puedo entender es que se explica por qué en todas partes se discute la cuestión de la figura del diamante (al menos 4 clases para crear un diamante) para explicar este comportamiento. Comprenda este problema usando solo 3 clases.

Por ejemplo, tengo clase A y clase B, estas dos clases son diferentes (no son clases secundarias de clase común) pero tienen un método común y se parecen a esto:

class A { void add(int a, int b) { } } class B { void add(int a, int b) { } }

Bien, ahora diga si Java admite herencia múltiple y si hay una clase que es la subclase de A y B como esta:

class C extends A,B{ //If this was possible @Override void add(int a, int b) { // TODO Auto-generated method stub super.add(a, b); //Which version of this, from A or B ? } }

entonces el compilador no podrá encontrar a qué método llamar desde A o B, y esa es la razón por la que Java no admite la herencia múltiple. Entonces, ¿hay algo malo con este concepto?

Cuando leí sobre este tema, pude entender el problema de Diamond, pero no puedo entender por qué las personas no dan ejemplo con tres clases (si es válida, porque usamos solo 3 clases para demostrar el problema, por lo que es fácil entiéndelo comparándolo con el tema Diamante.)

Déjeme saber si este ejemplo no es adecuado para explicar un problema o esto también se puede referir para entender un problema.

Edit: Tengo una votación cerrada aquí que indica que la pregunta no está clara. Aquí está la pregunta principal:

¿Puedo entender por qué "Java no admite herencia múltiple" con 3 clases solo como se describe anteriormente o debo tener 4 clases (estructura Diamond) para comprender el problema?


El problema del diamante con cuatro clases es más simple que el problema de tres clases descrito en la pregunta.

El problema con tres clases agrega otro problema que debe resolverse primero: el conflicto de nombres causado por dos métodos de add no relacionados con la misma firma. No es realmente difícil de resolver, pero agrega una complejidad innecesaria. Probablemente se permita en Java (como ya se ha permitido implementar múltiples métodos de interfaz no relacionados con la misma firma), pero podría haber lenguajes que simplemente prohíban la herencia múltiple de métodos similares sin un ancestro común.

Al agregar una cuarta clase que define la add , queda claro que tanto A como B están implementando el mismo método de add .

Por lo tanto, el problema del diamante es más claro porque muestra el problema utilizando herencia simple en lugar de usar métodos con la misma firma. Es bien conocido y probablemente se aplique a una gama más amplia de lenguajes de programación, por lo que la variación de éste con tres clases (lo que agrega el problema adicional de conflicto de nombres) no tiene mucho sentido y, por lo tanto, no se ha adoptado.


El problema del diamante que usted cita es una razón (y una muy buena) para no admitir la herencia múltiple. También hay otras razones, que puedes leer un poco aquí .

Muchas de las razones se reducen a la complejidad (es bastante complejo hacerlo bien), la necesidad legítima relativamente rara de usarla y todo tipo de otros problemas con el envío (aparte del problema del diamante) que crea.


Esa es solo una dificultad que tiene que resolver para la herencia múltiple en un idioma. Como existen lenguajes que tienen herencia múltiple (por ejemplo, Common Lisp, C ++, Eiffel), obviamente no es insuperable.

Common Lisp define la estrategia exacta para priorizar (ordenar) el gráfico de herencia, por lo que no hay ambigüedad en los casos raros en los que sí importa en la práctica.

C ++ utiliza la herencia virtual (aún no he hecho el esfuerzo de entender lo que eso significa).

Eiffel permite especificar exactamente cómo desea heredar, posiblemente cambiando el nombre de los métodos en la subclase.

Java acaba de saltar herencia múltiple. (Personalmente, creo que es difícil no insultar a sus diseñadores al intentar racionalizar esta decisión. Por supuesto, es difícil pensar en una cosa cuando el lenguaje en el que piensa no lo admite).


Java no admite la herencia múltiple porque los diseñadores del lenguaje diseñaron Java de esta manera. Otros lenguajes como C ++ admiten la herencia múltiple simplemente bien, por lo que no es un problema técnico sino solo un criterio de diseño.

El problema con la herencia múltiple es que no siempre está claro a qué método desde qué clase está llamando y a qué variables de instancia está accediendo. Diferentes personas lo interpretan de manera diferente y los diseñadores de Java creían en ese momento que era mejor omitir la herencia múltiple por completo.

C ++ resuelve el problema de la clase con forma de diamante con herencia virtual :

La herencia virtual es una técnica utilizada en la programación orientada a objetos, donde se declara que una clase base particular en una jerarquía de herencia comparte sus instancias de datos miembro con cualquier otra inclusión de esa misma base en otras clases derivadas. Por ejemplo, si la clase A normalmente se deriva (no virtualmente) de la clase X (se supone que contiene miembros de datos), y la clase B también, y la clase C se hereda de ambas clases A y B, contendrá dos conjuntos de los miembros de datos asociado con la clase X (accesible de forma independiente, a menudo con calificadores desambiguantes adecuados). Pero si la clase A se deriva virtualmente de la clase X, los objetos de la clase C contendrán solo un conjunto de miembros de datos de la clase X. El lenguaje más conocido que implementa esta característica es C ++.

Contrariamente a Java, en C ++ puede deshabilitar qué método de instancia llamar mediante el prefijo de la llamada con el nombre de la clase:

class X { public: virtual void f() { } }; class Y : public X { public: virtual void f() { } }; class Z : public Y { public: virtual void f() { X::f(); } };


La herencia múltiple no es un problema si encuentra una solución sensata para la resolución de métodos. Cuando invocas un método en un objeto, la resolución del método es el acto de elegir qué clase de versión del método se debe usar. Para esto, el gráfico de herencia está linealizado. Luego, se elige la primera clase en esta secuencia que proporciona una implementación del método solicitado.

Para ilustrar los siguientes ejemplos de estrategias de resolución de conflictos, usaremos este gráfico de herencia de diamante:

+-----+ | A | |=====| |foo()| +-----+ ^ | +---+---+ | | +-----+ +-----+ | B | | C | |=====| |=====| |foo()| |foo()| +-----+ +-----+ ^ ^ | | +---+---+ | +-----+ | D | |=====| +-----+

  • La estrategia más flexible es requerir que el programador elija explícitamente la implementación al crear una clase ambigua, al anular explícitamente el método conflictivo. Una variante de esto es prohibir la herencia múltiple. Si un programador desea heredar el comportamiento de varias clases, tendrá que usarse la composición y se escribirán varios métodos proxy. Sin embargo, los conflictos de herencia ingenuamente resueltos tienen los mismos inconvenientes que ...

  • Primera búsqueda en profundidad , que podría crear la linealización D, B, A, C Pero de esta manera, A::foo() se considera antes de C::foo() aunque C::foo() anula A::foo() . Esto no puede ser lo que queríamos. Un ejemplo de un lenguaje que usa DFS es Perl.

  • Use un algoritmo inteligente que garantice que si X es una subclase de Y , siempre aparecerá antes de Y en la linealización. Dicho algoritmo no podrá desentrañar todos los gráficos de herencia, pero proporciona una semántica sensata en la mayoría de los casos: si una clase anula un método, siempre será preferible al método anulado. Este algoritmo existe y se llama C3 . Esto crearía la linealización D, B, C, A C3 se presentó por primera vez en 1996. Desafortunadamente, Java se publicó en 1995, por lo que C3 no se conocía cuando Java se diseñó inicialmente.

  • Utilice la composición, no la herencia - revisado. Algunas soluciones a la herencia múltiple sugieren deshacerse del bit de "herencia de clase" y, en cambio, proponen otras unidades de composición. Un ejemplo es mixins , que "copia y pega" las definiciones de métodos en su clase. Esto es increíblemente crudo.

    La idea de los mixins se ha refinado en traits (presentado en 2002, también demasiado tarde para Java). Los rasgos son un caso más general de ambas clases e interfaces. Cuando “hereda” un rasgo, las definiciones se insertan en su clase, de modo que esto no complique la resolución del método. A diferencia de los mixins, los rasgos proporcionan estrategias más matizadas para resolver conflictos. Especialmente, el orden en que se componen los rasgos importa. Los rasgos desempeñan un papel destacado en el sistema de objetos "Moose" de Perl (llamados roles ) y en Scala .

Java prefiere la herencia única por otro motivo: si cada clase solo puede tener una superclase, no tenemos que aplicar algoritmos de resolución de métodos complicados: la cadena de herencia ya es el orden de resolución de métodos. Esto hace que los métodos sean un poco más eficientes.

Java 8 introdujo métodos predeterminados que se parecen a los rasgos. Sin embargo, las reglas de la resolución de métodos de Java hacen que las interfaces con los métodos predeterminados sean mucho menos capaces que los rasgos. Aún así, un paso en la dirección de adaptar soluciones modernas al problema de herencia múltiple.

En la mayoría de los esquemas de resolución de métodos de herencia múltiple, el orden de las superclases es importante. Es decir, hay una diferencia entre la class D extends B, C y la class D extends C, B Debido a que el orden se puede usar para una simple desambiguación, un ejemplo de tres clases no demuestra suficientemente los problemas asociados con la herencia múltiple. Necesitas un problema completo de diamantes de cuatro clases para eso, ya que muestra cómo la búsqueda ingenua de profundidad conduce a un orden de resolución de métodos poco intuitivo.