Vorausgesetzt, ich habe eine Klassenbasis, die einen einzelnen Argumentkonstruktor mit einem TextBox-Objekt als Argument hat. Wenn ich eine Klasse Simple der folgenden Form habe:
public class Simple extends Base {
public Simple(){
TextBox t = new TextBox();
super(t);
//wouldn't it be nice if I could do things with t down here?
}
}
Ich erhalte eine Fehlermeldung, dass der Aufruf von super der erste Aufruf in einem Konstruktor sein muss. Seltsamerweise kann ich dies jedoch tun.
public class Simple extends Base {
public Simple(){
super(new TextBox());
}
}
Warum ist dies zulässig, das erste Beispiel jedoch nicht? Ich kann verstehen, dass zuerst die Unterklasse eingerichtet werden muss und möglicherweise nicht zugelassen werden kann, dass Objektvariablen instanziiert werden, bevor der Superkonstruktor aufgerufen wird. Aber t ist eindeutig eine (lokale) Methodenvariable. Warum also nicht zulassen?
Gibt es eine Möglichkeit, diese Einschränkung zu umgehen? Gibt es eine gute und sichere Möglichkeit, Variablen für Dinge zu speichern, die Sie möglicherweise konstruieren, bevor Sie super aufrufen, aber nachdem Sie den Konstruktor eingegeben haben? Oder allgemeiner gesagt, die Berechnung kann durchgeführt werden, bevor super tatsächlich aufgerufen wird, aber innerhalb des Konstruktors?
Vielen Dank.
Antworten:
Ja, es gibt eine Problemumgehung für Ihren einfachen Fall. Sie können einen privaten Konstruktor erstellen, der
TextBox
als Argument verwendet wird, und diesen von Ihrem öffentlichen Konstruktor aus aufrufen.public class Simple extends Base { private Simple(TextBox t) { super(t); // continue doing stuff with t here } public Simple() { this(new TextBox()); } }
Für kompliziertere Dinge müssen Sie eine Factory- oder eine statische Factory-Methode verwenden.
quelle
super()
die im Konstruktor nach nichts anderem angezeigt werden, so gut wie vollständig umgehen können . In extremen Fällen können Sie sogar mehrere private Konstruktoren miteinander verketten, obwohl ich mir keine Situation vorstellen kann, in der ich das tun müsste, und wenn ich das tun würde, würde ich mich wahrscheinlich fragen, ob es einen besseren Weg gibt, um das zu erreichen, was ich habe Hat gemacht.super(this.getClass())
, aber da Sie nicht referenzieren dürfenthis
, war dieser Code illegal, obwohl es durchaus vernünftig erscheint, dass ich an dieser Stelle die tatsächliche Klasse kennen sollte.getClass()
Methode als Sonderfall in der JLS zu behandeln. Verwenden Sie<classname>.class
stattdessen.<classname>.class
war in diesem Fall keine Option, da ich eine abstrakte Klasse schrieb, die erweitert werden konnte, und die eigentliche Klasse der Instanz benötigte.Ich hatte das gleiche Problem mit der Berechnung vor dem Super Call. Manchmal möchten Sie einige Bedingungen überprüfen, bevor Sie anrufen
super()
. Sie haben beispielsweise eine Klasse, die beim Erstellen viele Ressourcen verwendet. Die Unterklasse möchte einige zusätzliche Daten und möchte diese möglicherweise zuerst überprüfen, bevor sie den Superkonstruktor aufruft. Es gibt einen einfachen Weg, um dieses Problem zu umgehen. mag ein bisschen seltsam aussehen, aber es funktioniert gut:Verwenden Sie eine private statische Methode in Ihrer Klasse, die das Argument des Superkonstruktors zurückgibt, und führen Sie Ihre Überprüfungen in folgenden Schritten durch:
public class Simple extends Base { public Simple(){ super(createTextBox()); } private static TextBox createTextBox() { TextBox t = new TextBox(); t.doSomething(); // ... or more return t; } }
quelle
Es ist erforderlich , die durch die Sprache , um zu gewährleisten , dass die übergeordnete Klasse zuverlässig aufgebaut ist , zuerst . Insbesondere: "Wenn ein Konstruktor einen Superklassenkonstruktor nicht explizit aufruft, fügt der Java-Compiler automatisch einen Aufruf an den Konstruktor ohne Argumente der Superklasse ein."
In Ihrem Beispiel hängt die Oberklasse möglicherweise vom Stand der
t
Bauzeit ab. Sie können später jederzeit eine Kopie anfordern.Hier und hier gibt es eine ausführliche Diskussion .
quelle
Sie können ein statisches Lieferanten-Lambda definieren, das eine kompliziertere Logik enthalten kann.
public class MyClass { private static Supplier<MyType> myTypeSupplier = () -> { return new MyType(); }; public MyClass() { super(clientConfig, myTypeSupplier.get()); } }
quelle
Der Grund, warum das zweite Beispiel erlaubt ist, aber nicht das erste, ist höchstwahrscheinlich, dass die Sprache aufgeräumt bleibt und keine seltsamen Regeln eingeführt werden.
Es wäre gefährlich, Code laufen zu lassen, bevor super aufgerufen wurde, da Sie möglicherweise mit Dingen herumspielen, die hätten initialisiert werden sollen, aber noch nicht waren. Grundsätzlich denke ich, dass Sie im Aufruf von Super selbst ziemlich viele Dinge tun können (z. B. eine statische Methode zum Berechnen einiger Dinge aufrufen, die an den Konstruktor gehen müssen), aber Sie werden niemals in der Lage sein, etwas von dem Nicht zu verwenden -doch-vollständig konstruiertes Objekt, was eine gute Sache ist.
quelle
So funktioniert Java :-) Es gibt technische Gründe, warum es so gewählt wurde. Es mag in der Tat seltsam sein, dass Sie vor dem Aufruf von super keine Berechnungen für Einheimische durchführen können, aber in Java muss das Objekt zuerst zugewiesen werden und muss daher bis zum Objekt reichen, damit alle Felder korrekt initialisiert werden, bevor Sie versehentlich Änderungen vornehmen können Sie.
In Ihrem Fall gibt es meistens einen Getter, mit dem Sie auf den Parameter zugreifen können, den Sie super () gegeben haben. Also würden Sie dies verwenden:
quelle
final
Instanz-Methode aufrufen könnte , die von einer Unterklasse überschrieben wird. Bei der Überschreibung werden dann keine initialisierten Felder aus der Unterklasse angezeigt (auch keinefinal
mit Instanzinitialisierern!). Einige statische Analysewerkzeuge warnen zu Recht vor dieser Situation.Dies ist meine Lösung, mit der Sie zusätzliche Objekte erstellen und ändern können, ohne zusätzliche Klassen, Felder, Methoden usw. zu erstellen.
class TextBox { } class Base { public Base(TextBox textBox) { } } public class Simple extends Base { public Simple() { super(((Supplier<TextBox>) () -> { var textBox = new TextBox(); //some logic with text box return textBox; }).get()); } }
quelle