Ist es möglich, im Konstruktor vor super () zu rechnen?

79

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.

Stephen Cagle
quelle
3
aus welchem ​​möglichen grund wurde dies als gwt markiert? Weil du es in GWT versucht hast?
2
TextBox war eine GWT-Klasse, aber nein, es ist vermutlich nicht relevant.
Stephen Cagle

Antworten:

89

Ja, es gibt eine Problemumgehung für Ihren einfachen Fall. Sie können einen privaten Konstruktor erstellen, der TextBoxals 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.

Seidenschwanz
quelle
1
Dies scheint eine gute Antwort auf die Frage zu sein, wie Sie die Verweise auf Objekte verwenden würden, die nach der Erstellung, aber vor Super erstellt wurden.
Stephen Cagle
3
Dies ist ein sehr praktisches Muster, mit dem Sie die Anforderungen, 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.
Laurence Gonsalves
Jep. Andere hatten bereits geantwortet, warum es in Java so funktioniert. Ich wollte nur auf eine bestimmte Problemumgehung hinweisen. Ich denke, dass es für die meisten anderen gültigen Fälle auch möglich ist, eine akzeptable Problemumgehung zu finden. Um mir selbst zu widersprechen, gibt es einen Fall, in dem mich das gebissen hat: Ich habe eine Klasse erweitert, deren Konstruktor einen Class-Parameter hatte. Ich wollte anrufen super(this.getClass()), aber da Sie nicht referenzieren dürfen this, war dieser Code illegal, obwohl es durchaus vernünftig erscheint, dass ich an dieser Stelle die tatsächliche Klasse kennen sollte.
Waxwing
@waxwing - es mag "vollkommen vernünftig" erscheinen, aber es würde bedeuten, die getClass()Methode als Sonderfall in der JLS zu behandeln. Verwenden Sie <classname>.classstattdessen.
Stephen C
Die Verwendung <classname>.classwar in diesem Fall keine Option, da ich eine abstrakte Klasse schrieb, die erweitert werden konnte, und die eigentliche Klasse der Instanz benötigte.
Waxwing
32

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;
  }
}
Ben
quelle
8

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 tBauzeit ab. Sie können später jederzeit eine Kopie anfordern.

Hier und hier gibt es eine ausführliche Diskussion .

Müllgott
quelle
2
Interessanterweise scheint Java alles zu tun, um einen der wichtigsten Fälle zu verhindern, in denen das Umschließen eines Konstruktoraufrufs der Oberklasse mit Code nützlich wäre: Sicherstellen, dass Dinge bereinigt werden können, wenn ein Konstruktor eine Ausnahme auslöst. Wenn beispielsweise einer der Konstruktorüberladungen eines Objekts Daten aus einer übergebenen Datei ausliest, möchte ein Konstruktor einer abgeleiteten Klasse möglicherweise eine Überladung, die einen Dateinamen akzeptiert, dem Konstruktor der Oberklasse eine neu geöffnete Datei übergibt und die Datei dann schließt nachher. Wie kann die Datei geschlossen werden, wenn der Konstruktor der Oberklasse auslöst?
Superkatze
@supercat: Sie müssen für die Vererbung entwerfen oder sie ausschließen [Bloch, EffectiveJava , §17]; Siehe auch diese Fragen und Antworten .
Trashgod
Wenn die Konstruktion eines Unterklassenobjekts das Muster "Ressource erwerben; Superklassenobjekt erstellen; Ressource freigeben" erfordert, wie sollte dies implementiert werden? Übergeben Sie dem inneren Konstruktor ein Objekt, das eine Single-Interface-Methode implementiert, die im Fehlerfall verwendet wird? Vage praktikabel, vielleicht auf Kosten der Verkettung einiger zusätzlicher Ebenen von Konstruktoraufrufen.
Supercat
Ich hatte den Luxus, die Oberklasse zu reparieren oder Kompositionen zu verwenden; Pingen Sie mich an, wenn Sie dies als Frage stellen.
Trashgod
1

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());
    }
}
loweryjk
quelle
0

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.

CodingInsomnia
quelle
0

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:

super (neue TextBox ());
final TextBox box = getWidget ();
... mach dein Ding...
David Nouls
quelle
2
Dass "alle Felder korrekt initialisiert sind", ist in Java nicht garantiert (im Gegensatz zu IIUC, C ++), da ein Konstruktor der Oberklasse eine Nicht- finalInstanz-Methode aufrufen könnte , die von einer Unterklasse überschrieben wird. Bei der Überschreibung werden dann keine initialisierten Felder aus der Unterklasse angezeigt (auch keine finalmit Instanzinitialisierern!). Einige statische Analysewerkzeuge warnen zu Recht vor dieser Situation.
Jesse Glick
0

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());
    }
}
Pavel_K
quelle