ventajas - ¿Por qué Java no permite la herencia múltiple, pero sí permite adaptarse a múltiples interfaces con implementaciones predeterminadas?




ventajas y desventajas de la herencia multiple (4)

No estoy preguntando esto -> ¿Por qué no hay herencia múltiple en Java, pero se permite la implementación de múltiples interfaces?

En Java, no se permite la herencia múltiple, pero, después de Java 8, las interfaces pueden tener métodos predeterminados (pueden implementar métodos por sí mismos), al igual que las clases abstractas. En este contexto, también debe permitirse la herencia múltiple.

interface TestInterface 
{ 
    // abstract method 
    public void square(int a); 

    // default method 
    default void show() 
    { 
      System.out.println("Default Method Executed"); 
    } 
} 

Eso está relacionado principalmente con el "problema de los diamantes", creo. En este momento, si implementa varias interfaces con el mismo método, el compilador lo obliga a anular el método que desea implementar, porque no sabe cuál usar. Supongo que los creadores de Java querían eliminar este problema cuando las interfaces no podían usar los métodos predeterminados. Ahora se les ocurrió una idea, es bueno poder tener métodos con implementación en interfaces, ya que aún puede usarlos como interfaces funcionales en expresiones streams / lambda y utilizar sus métodos predeterminados en el procesamiento. No se puede hacer eso con clases, pero el problema del diamante todavía existe allí. Esa es mi opinión :)


Java no permite la herencia múltiple para los campos. Esto sería difícil de admitir en la JVM ya que solo puede tener referencias al inicio de un objeto donde se encuentra el encabezado, no a ubicaciones de memoria arbitrarias.

En Oracle / Openjdk, los objetos tienen un encabezado seguido de los campos de la súper clase, luego de la siguiente súper clase, etc. Sería un cambio significativo permitir que los campos de una clase aparezcan en diferentes desplazamientos en relación con el encabezado de un objeto para diferentes subclases. Las referencias de objetos más probables tendrían que convertirse en una referencia al encabezado del objeto y una referencia a los campos para admitir esto.


Los diseñadores de lenguaje ya pensaron en eso, por lo que estas cosas son impuestas por el compilador. Así que si define:

interface First {
    default void go() {
    }
}

interface Second {
    default void go() {
    }
}

E implementas una clase para ambas interfaces:

static class Impl implements First, Second {

}

obtendrá un error de compilación; y tendría que anular go para no crear la ambigüedad a su alrededor.

Pero podrías estar pensando que puedes engañar al compilador aquí, haciendo:

interface First {
    public default void go() {
    }
}

static abstract class Second {
    abstract void go();
}

static class Impl extends Second implements First {
}

Podría pensar que First::go ya proporciona una implementación para Second::go y debería estar bien. Esto es demasiado cuidado, por lo que tampoco compila.

JLS 9.4.1.3: Del mismo modo, cuando se hereda un método abstracto y uno predeterminado con firmas coincidentes, se produce un error . En este caso, sería posible dar prioridad a uno u otro, quizás supongamos que el método predeterminado también proporciona una implementación razonable para el método abstracto. Pero esto es arriesgado, ya que, aparte del nombre y la firma coincidentes, no tenemos motivos para creer que el método predeterminado se comporte de manera coherente con el contrato del método abstracto; es posible que el método predeterminado no haya existido cuando se desarrolló originalmente la subinterfaz . Es más seguro en esta situación pedirle al usuario que afirme activamente que la implementación predeterminada es apropiada (a través de una declaración de anulación).

El último punto que traería para solidificar que no se permite la herencia múltiple, incluso con las nuevas adiciones en java, es que los métodos estáticos de las interfaces no se heredan. Los métodos estáticos se heredan de forma predeterminada:

static class Bug {
    static void printIt() {
        System.out.println("Bug...");
    }
}

static class Spectre extends Bug {
    static void test() {
        printIt(); // this will work just fine
    }
}

Pero si cambiamos eso por una interfaz (y usted puede implementar múltiples interfaces, a diferencia de las clases):

interface Bug {
    static void printIt() {
        System.out.println("Bug...");
    }
}

static class Spectre implements Bug {
    static void test() {
        printIt(); // this will not compile
    }
}

Ahora, esto está prohibido por el compilador y JLS también:

JLS 8.4.8: una clase no hereda métodos estáticos de sus superinterfaces.


Los principales problemas con la herencia múltiple son el ordenamiento (para anular y llamar a super ), campos y constructores; Las interfaces no tienen campos o constructores, por lo que no causan problemas.

Si nos fijamos en otros idiomas, generalmente se dividen en dos categorías amplias:

  1. Idiomas con herencia múltiple más algunas características para desambiguar casos especiales: herencia virtual [C ++], llamadas directas a todos los superconstructores en la clase más derivada [C ++], linealización de superclases [Python], reglas complejas para super [Python], etc. .

  2. Lenguajes con un concepto diferente, generalmente llamados interfaces , rasgos , combinaciones , módulos , etc. que imponen algunas limitaciones, tales como: sin constructores [Java] o sin constructores con parámetros [Scala hasta hace muy poco tiempo], sin campos mutables [Java], específicos reglas para anular (por ejemplo, los mixins tienen prioridad sobre las clases base [Ruby] para que pueda incluirlos cuando necesite un montón de métodos de utilidad), etc. Java se ha convertido en un lenguaje como estos.

¿Por qué simplemente al rechazar campos y constructores resuelves muchos problemas relacionados con la herencia múltiple?

  • No puedes tener campos duplicados en clases base duplicadas.
    • La jerarquía de clases principal sigue siendo lineal.
  • No puedes construir tus objetos base de manera incorrecta.
    • Imagínese si el objeto tuviera campos públicos / protegidos y todas las subclases tuvieran constructores que establecieran esos campos. Cuando hereda de más de una clase (todas derivadas de Objeto), ¿cuál puede configurar los campos? ¿La última clase? Se convierten en hermanos en la jerarquía, por lo que no saben nada el uno del otro. ¿Deberías tener múltiples copias de Objeto para evitar esto? ¿Interoperarían todas las clases correctamente?
  • Recuerde que los campos en Java no son virtuales (reemplazables), son simplemente almacenamiento de datos.
    • Podría hacer un lenguaje en el que los campos se comporten como métodos y se pueda anular (el almacenamiento real siempre sería privado), pero ese sería un cambio mucho mayor y probablemente ya no se llamaría Java.
  • Las interfaces no pueden ser instanciadas por sí mismas.
    • Siempre debes combinarlos con una clase concreta. Eso elimina la necesidad de constructores y aclara también la intención del programador (es decir, lo que se supone que es una clase concreta y lo que es una interfaz / combinación de accesorios). Esto también proporciona un lugar bien definido para resolver todas las ambigüedades: la clase concreta.




abstract