multiple - interface java
¿Por qué Java no permite la herencia múltiple, pero sí permite adaptarse a múltiples interfaces con implementaciones predeterminadas? (6)
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.
Las cosas no son tan simples.
Si una clase implementa varias interfaces que definen métodos predeterminados con la misma firma, el compilador lo obligará a anular este método para la clase.
Por ejemplo con estas dos interfaces:
public interface Foo {
default void doThat() {
// ...
}
}
public interface Bar {
default void doThat() {
// ...
}
}
No se compilará:
public class FooBar implements Foo, Bar{
}
Debe definir / anular el método para eliminar la ambigüedad.
Por ejemplo, podría delegar en la implementación de la Bar
, como:
public class FooBar implements Foo, Bar{
@Override
public void doThat() {
Bar.super.doThat();
}
}
o delegar en la implementación de Foo
como:
public class FooBar implements Foo, Bar {
@Override
public void doThat() {
Foo.super.doThat();
}
}
O aún definir otro comportamiento:
public class FooBar implements Foo, Bar {
@Override
public void doThat() {
// ...
}
}
Esa restricción muestra que Java no permite la herencia múltiple, incluso para los métodos predeterminados de la interfaz.
Creo que no podemos aplicar la misma lógica para múltiples herencias porque pueden ocurrir múltiples problemas, los principales son:
- anular el método de una clase heredada podría introducir efectos secundarios y cambiar el comportamiento general de la clase heredada si la clase se basa internamente en este método.
- ¿Cómo heredar múltiples campos? E incluso si el idioma lo permitiera, tendría exactamente el mismo problema que el citado anteriormente: efecto secundario en el comportamiento de la clase heredada: un campo
int foo
definido en una claseA
yB
que desea subclase no tiene el Mismo significado e intención.
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:
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. .
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.
default
métodos default
en las interfaces plantean un problema que:
Si ambas interfaces implementadas definen un método predeterminado con la misma firma de método, la clase de implementación no sabe qué método predeterminado usar.
La clase de implementación debe definir explícitamente especificar qué método predeterminado usar o definir su propio método.
Por lo tanto, default
métodos default
en Java-8 no facilitan la herencia múltiple. La principal motivación detrás de los métodos predeterminados es que si en algún momento necesitamos agregar un método a una interfaz existente, podemos agregar un método sin cambiar las clases de implementación existentes. De esta manera, la interfaz sigue siendo compatible con versiones anteriores. Sin embargo, debemos recordar la motivación de usar métodos predeterminados y mantener la separación de la interfaz y la implementación.