it-swarm.com.de

Vererbung des Java-Konstruktors

Ich habe mich gefragt, warum in Java Konstruktoren nicht vererbt werden? Sie wissen, wenn Sie eine solche Klasse haben:

public class Super {

  public Super(ServiceA serviceA, ServiceB serviceB, ServiceC serviceC){
    this.serviceA = serviceA;
    //etc
  } 

}

Wenn Sie später von Super erben, wird Java sich darüber beschweren, dass kein Standardkonstruktor definiert ist. Die Lösung ist offensichtlich so etwas wie:

public class Son extends Super{

  public Son(ServiceA serviceA, ServiceB serviceB, ServiceC serviceC){
    super(serviceA,serviceB,serviceC);
  }

}

Dieser Code ist wiederholend, nicht DRY und nutzlos (IMHO) ... also bringt es die Frage noch einmal:

Warum unterstützt Java keine Konstruktor-Vererbung? Hat es einen Vorteil, diese Erbschaft nicht zuzulassen?

169
Pablo Fernandez

Angenommen, Konstruktoren wurden vererbt ... dann, weil jede Klasse schließlich von Object stammt, jede Klasse würde einen parameterlosen Konstruktor erhalten. Das ist eine schlechte Idee. Was genau würdest du erwarten:

FileInputStream stream = new FileInputStream();

machen?

Jetzt sollte es möglicherweise eine Möglichkeit geben, die "Pass-Through" -Konstruktoren leicht zu erstellen, die recht häufig sind, aber ich denke nicht, dass dies der Standard sein sollte. Die zum Erstellen einer Unterklasse erforderlichen Parameter unterscheiden sich häufig von den für die Oberklasse erforderlichen Parametern.

188
Jon Skeet

Wenn Sie von Super erben, geschieht dies in Wirklichkeit:

public class Son extends Super{

  // If you dont declare a constructor of any type, adefault one will appear.
  public Son(){
    // If you dont call any other constructor in the first line a call to super() will be placed instead.
    super();
  }

}

Das ist der Grund, weil Sie Ihren eindeutigen Konstruktor aufrufen müssen, da "Super" keinen Standardkonstruktor hat.

Versuchen Sie zu erraten, warum Java die Konstruktor-Vererbung nicht unterstützt, wahrscheinlich weil ein Konstruktor nur dann sinnvoll ist, wenn es um konkrete Instanzen geht, und Sie sollten keine Instanz von etwas erstellen können, wenn Sie nicht wissen, wie er definiert ist (durch Polymorphismus).

27

Das Erstellen Ihres Unterklassenobjekts kann auf eine andere Weise erfolgen als Ihre Superklasse. Möglicherweise möchten Sie nicht, dass Clients der Unterklasse bestimmte Konstruktoren aufrufen können, die in der Superklasse verfügbar sind. 

Ein dummes Beispiel:

class Super {
    protected final Number value;
    public Super(Number value){
        this.value = value;
    }
}

class Sub {
    public Sub(){ super(Integer.valueOf(0)); }
    void doSomeStuff(){
        // We know this.value is an Integer, so it's safe to cast.
        doSomethingWithAnInteger((Integer)this.value);
    }
}

// Client code:
Sub s = new Sub(Long.valueOf(666L)): // Devilish invocation of Super constructor!
s.doSomeStuff(); // throws ClassCastException

Oder noch einfacher:

class Super {
    private final String msg;
    Super(String msg){
        if (msg == null) throw new NullPointerException();
        this.msg = msg;
    }
}
class Sub {
    private final String detail;
    Sub(String msg, String detail){
        super(msg);
        if (detail == null) throw new NullPointerException();
        this.detail = detail;
    }
    void print(){
        // detail is never null, so this method won't fail
        System.out.println(detail.concat(": ").concat(msg));
    }
}
// Client code:
Sub s = new Sub("message"); // Calling Super constructor - detail is never initialized!
s.print(); // throws NullPointerException

In diesem Beispiel sehen Sie, dass Sie eine Möglichkeit benötigen, zu deklarieren, dass "Ich möchte diese Konstruktoren übernehmen" oder "Ich möchte alle Konstruktoren außer diesen" erben, und dann müssen Sie auch eine Standard-Konstruktor-Vererbung angeben Präferenz nur für den Fall, dass jemand einen neuen Konstruktor in der Superklasse hinzufügt ... oder Sie müssen nur die Konstruktoren aus der Superklasse wiederholen, wenn Sie sie "erben" möchten, was offensichtlich die naheliegendste Methode ist. 

11
gustafc

Da Konstruktoren ein Implementierungsdetail sind, handelt es sich dabei nicht um Objekte, die ein Benutzer einer Ober-/Superklasse überhaupt aufrufen kann. Zu der Zeit, zu der sie eine Instanz bekommen, ist sie bereits aufgebaut; und umgekehrt, zum Zeitpunkt der Erstellung eines Objekts gibt es per Definition keine Variable, der es aktuell zugewiesen ist.

Überlegen Sie, was es bedeuten würde, alle Unterklassen zu einem geerbten Konstruktor zu zwingen. Ich behaupte, dass es klarer ist, die Variablen direkt zu übergeben, als dass die Klasse einen Konstruktor mit einer bestimmten Anzahl von Argumenten "magisch" hat, nur weil es die Eltern sind.

5
Andrzej Doyle

Davids Antwort ist richtig. Ich möchte hinzufügen, dass Sie möglicherweise ein Zeichen von Gott erhalten, dass Ihr Entwurf durcheinander gerät und dass "Sohn" keine Unterklasse von "Super" sein sollte, sondern dass stattdessen einige Implementierungsdetails am besten ausgedrückt werden durch mit der Funktionalität, die Son bietet, als eine Art Strategie.

BEARBEITEN: Jon Skeet's Antwort ist großartig.

2

Konstruktoren sind nicht polymorph.
Wenn Sie bereits erstellte Klassen verwenden, handelt es sich möglicherweise um den deklarierten Typ des Objekts oder um eine seiner Unterklassen. Dafür ist Vererbung nützlich.
Konstruktor wird immer für den spezifischen Typ aufgerufen, zB new String(). Hypothetische Unterklassen spielen dabei keine Rolle.

2
Rik

Denn eine (Super-) Klasse muss vollständige Kontrolle darüber haben, wie sie erstellt wird. Wenn der Programmierer entscheidet, dass es nicht sinnvoll ist, einen Standardkonstruktor (keine Argumente) als Teil des Klassenvertrags anzugeben, sollte der Compiler keinen solchen bereitstellen.

0
David R Tribble

Sie erben die Konstrukteure im Wesentlichen in dem Sinne, dass Sie einfach super aufrufen können, und wenn es angebracht ist, es wäre nur fehleranfällig, wenn andere es erwähnt haben, wenn dies standardmäßig der Fall ist. Der Compiler kann nicht voraussetzen, wann es angebracht ist und wann nicht. 

Die Aufgabe des Compilers besteht darin, so viel Flexibilität wie möglich bereitzustellen und gleichzeitig die Komplexität und das Risiko unbeabsichtigter Nebenwirkungen zu reduzieren.

0
clearlight