Java >> Programma Java >  >> Java

Perché Java non consente l'ereditarietà multipla ma consente la conformità a più interfacce con implementazioni predefinite

Le cose non sono così semplici.
Se una classe implementa più interfacce che definiscono metodi predefiniti con la stessa firma, il compilatore ti costringerà a sovrascrivere questo metodo per la classe.

Ad esempio con queste due interfacce:

public interface Foo {
    default void doThat() {
        // ...
    }
}

public interface Bar {    
    default void doThat() {
        // ...
    }       
}

Non verrà compilato:

public class FooBar implements Foo, Bar{
}

È necessario definire/sostituire il metodo per rimuovere l'ambiguità.
Potresti ad esempio delegare al Bar implementazione come :

public class FooBar implements Foo, Bar{    
    @Override
    public void doThat() {
        Bar.super.doThat();
    }    
}

o delegare al Foo implementazione come ::

public class FooBar implements Foo, Bar {
    @Override
    public void doThat() {
        Foo.super.doThat();
    }
}

o ancora definire un altro comportamento:

public class FooBar implements Foo, Bar {
    @Override
    public void doThat() {
        // ... 
    }
}

Tale vincolo mostra che Java non consente l'ereditarietà multipla anche per i metodi predefiniti dell'interfaccia.

Penso che non possiamo applicare la stessa logica per eredità multiple perché potrebbero verificarsi problemi multipli, i principali sono:

  • sovrascrivere/rimuovere l'ambiguità per un metodo in entrambe le classi ereditate potrebbe introdurre effetti collaterali e modificare il comportamento generale delle classi ereditate se si basano internamente su questo metodo. Con le interfacce predefinite esiste anche questo rischio, ma dovrebbe essere molto meno raro poiché i metodi predefiniti non sono progettati per introdurre elaborazioni complesse come più invocazioni interne all'interno della classe o per essere stateful (infatti le interfacce non possono ospitare il campo dell'istanza).
  • come ereditare più campi? E anche se la lingua lo consentisse, avresti esattamente lo stesso problema citato in precedenza:effetto collaterale nel comportamento della classe ereditata:a int foo campo definito in un A e B la classe che vuoi sottoclassare non ha lo stesso significato e intenzione.

I progettisti del linguaggio ci hanno già pensato, quindi queste cose vengono applicate dal compilatore. Quindi se definisci:

interface First {
    default void go() {
    }
}

interface Second {
    default void go() {
    }
}

E implementi una classe per entrambe le interfacce:

static class Impl implements First, Second {

}

otterrai un errore di compilazione; e dovresti sovrascrivere go per non creare ambiguità attorno ad esso.

Ma potresti pensare di poter ingannare il compilatore qui, facendo:

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

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

static class Impl extends Second implements First {
}

Potresti pensare che First::go fornisce già un'implementazione per Second::go e dovrebbe andare bene. Questo è troppo curato, quindi nemmeno questo viene compilato.

JLS 9.4.1.3:Allo stesso modo, quando vengono ereditati un abstract e un metodo predefinito con firme corrispondenti, produciamo un errore . In questo caso, sarebbe possibile dare priorità all'uno o all'altro - forse supponiamo che il metodo predefinito fornisca un'implementazione ragionevole anche per il metodo astratto. Ma questo è rischioso, poiché a parte il nome e la firma coincidenti, non abbiamo motivo di credere che il metodo predefinito si comporti in modo coerente con il contratto del metodo astratto - il metodo predefinito potrebbe non esistere nemmeno quando è stata originariamente sviluppata la sottointerfaccia . In questa situazione è più sicuro chiedere all'utente di affermare attivamente che l'implementazione predefinita è appropriata (tramite una dichiarazione di priorità).

L'ultimo punto che vorrei portare, per consolidare che l'ereditarietà multipla non è consentita anche con nuove aggiunte in Java, è che i metodi statici dalle interfacce non vengono ereditati. i metodi statici vengono ereditati per impostazione predefinita:

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

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

Ma se lo cambiamo per un'interfaccia (e puoi implementare più interfacce, a differenza delle classi):

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

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

Ora, questo è proibito dal compilatore e da JLS anche:

JLS 8.4.8:una classe non eredita i metodi statici dalle sue superinterfacce.


Java non consente l'ereditarietà multipla per i campi. Questo sarebbe difficile da supportare nella JVM poiché puoi solo avere riferimenti all'inizio di un oggetto in cui si trova l'intestazione, non posizioni di memoria arbitrarie.

In Oracle/Openjdk, gli oggetti hanno un'intestazione seguita dai campi della classe più super, quindi della classe successiva più super, ecc. Sarebbe un cambiamento significativo consentire ai campi di una classe di apparire con offset diversi rispetto all'intestazione di un oggetto per diverse sottoclassi. Molto probabilmente i riferimenti agli oggetti dovrebbero diventare un riferimento all'intestazione dell'oggetto e un riferimento ai campi per supportarlo.


Etichetta Java