744 Stimmen

Warum müssen this() und super() die erste Anweisung in einem Konstruktor sein?

Java schreibt vor, dass der Aufruf von this() oder super() in einem Konstruktor die erste Anweisung sein muss. Warum?

Zum Beispiel:

public class MyClass {
    public MyClass(int x) {}
}

public class MySubClass extends MyClass {
    public MySubClass(int a, int b) {
        int c = a + b;
        super(c);  // COMPILE ERROR
    }
}

Der Sun-Compiler sagt: "Der Aufruf von super muss die erste Anweisung im Konstruktor sein". Der Eclipse-Compiler sagt: "Constructor call must be the first statement in a constructor".

Sie können dies jedoch umgehen, indem Sie den Code ein wenig umgestalten:

public class MySubClass extends MyClass {
    public MySubClass(int a, int b) {
        super(a + b);  // OK
    }
}

Hier ein weiteres Beispiel:

public class MyClass {
    public MyClass(List list) {}
}

public class MySubClassA extends MyClass {
    public MySubClassA(Object item) {
        // Create a list that contains the item, and pass the list to super
        List list = new ArrayList();
        list.add(item);
        super(list);  // COMPILE ERROR
    }
}

public class MySubClassB extends MyClass {
    public MySubClassB(Object item) {
        // Create a list that contains the item, and pass the list to super
        super(Arrays.asList(new Object[] { item }));  // OK
    }
}

Es ist also Sie nicht daran hindern, die Logik auszuführen vor dem Anruf beim Hausmeister. Es hält Sie nur davon ab, Logik auszuführen, die Sie nicht in einen einzigen Ausdruck packen können.

Es gibt ähnliche Regeln für den Aufruf this() . Der Compiler sagt: "Der Aufruf von this muss die erste Anweisung im Konstruktor sein".

Warum hat der Compiler diese Einschränkungen? Können Sie ein Codebeispiel nennen, bei dem etwas Schlimmes passieren würde, wenn der Compiler diese Einschränkung nicht hätte?

9 Stimmen

Eine gute Frage. Ich habe ein ähnliches Projekt gestartet valjok.blogspot.com/2012/09/ und programmers.exchange, wo ich zeige, dass es Fälle gibt, in denen Unterfelder vor dem super() initialisiert werden müssen. Die Funktion erhöht also die Komplexität, wobei nicht klar ist, ob die positiven Auswirkungen in Bezug auf die "Codesicherheit" die negativen überwiegen. Ja, es gibt negative Konsequenzen, wenn super immer zuerst kommt. Überraschenderweise hat dies niemand erwähnt. Ich denke, dass dies eine konzeptionelle Angelegenheit ist und in programmers.exchange gefragt werden muss.

64 Stimmen

Das Schlimmste daran ist, dass dies eine reine Java-Beschränkung ist. Auf der Bytecode-Ebene gibt es keine solche Einschränkung.

2 Stimmen

Nun, es wäre unmöglich, diese Einschränkung auf der Bytecode-Ebene zu haben - alle Beispiele in diesem Beitrag würden eine solche Einschränkung verletzen, auch die, die die gesamte Logik in einen einzigen Ausdruck packen.

9voto

Kate Gregory Punkte 18638

Du hast gefragt, warum, und die anderen Antworten sagen imo nicht wirklich, warum es in Ordnung ist, den Konstruktor deines Super aufzurufen, aber nur, wenn es die allererste Zeile ist. Der Grund ist, dass du nicht wirklich aufrufen den Konstrukteur. In C++ lautet die entsprechende Syntax

MySubClass: MyClass {

public:

 MySubClass(int a, int b): MyClass(a+b)
 {
 }

};

Wenn Sie die Initialisierungsklausel allein vor der öffnenden Klammer sehen, wissen Sie, dass sie etwas Besonderes ist. Sie wird ausgeführt, bevor der Rest des Konstruktors ausgeführt wird und sogar bevor die Mitgliedsvariablen initialisiert werden. Bei Java ist es nicht viel anders. Es gibt eine Möglichkeit, einen Teil des Codes (andere Konstruktoren) auszuführen, bevor der Konstruktor wirklich beginnt, bevor irgendwelche Mitglieder der Unterklasse initialisiert werden. Und dieser Weg ist, den "Aufruf" (z.B. super ) gleich in der ersten Zeile. (In gewisser Weise ist das super o this ist sozusagen vor der ersten geschweiften Klammer, auch wenn Sie ihn danach eingeben, weil er ausgeführt wird, bevor Sie zu dem Punkt kommen, an dem alles vollständig aufgebaut ist). Jeder andere Code nach der öffnenden Klammer (wie int c = a + b; ) lässt den Compiler sagen: "Oh, ok, keine anderen Konstruktoren, dann können wir alles initialisieren." Also läuft er los und initialisiert Ihre Superklasse und Ihre Mitglieder und so weiter und beginnt dann mit der Ausführung des Codes nach der offenen Klammer.

Wenn ein paar Zeilen später ein Code auftaucht, der sagt: "Oh ja, wenn du dieses Objekt konstruierst, hier sind die Parameter, die du an den Konstruktor der Basisklasse weitergeben sollst", ist es zu spät und es macht keinen Sinn mehr. Sie erhalten also einen Compilerfehler.

2 Stimmen

1. Wenn die Java-Entwickler wollten, dass der Superkonstruktor implizit ist, könnten sie das einfach tun, und, was noch wichtiger ist, dies erklärt nicht, warum ein impliziter Superkonstruktor sehr nützlich ist. 2. IMO ist Ihr Kommentar, dass es keinen Sinn macht, nicht sinnvoll. Ich erinnere mich, dass ich das gebraucht habe. Können Sie beweisen, dass ich etwas Sinnloses getan habe?

2 Stimmen

Stellen Sie sich vor, Sie müssen einen Raum betreten. Die Tür ist verschlossen, also schlagen Sie ein Fenster ein, greifen hinein und verschaffen sich Zutritt. Drinnen, auf halbem Weg durch den Raum, finden Sie einen Zettel mit einem Schlüssel, den Sie auf dem Weg nach drinnen benutzen sollen. Aber Sie sind ja schon drin. Ähnlich verhält es sich, wenn der Compiler auf halbem Weg durch die Ausführung eines Konstruktors auf den Hinweis stößt: "Hier steht, was mit diesen Parametern zu tun ist, bevor der Konstruktor ausgeführt wird", was soll er tun?

2 Stimmen

Wenn es in Wirklichkeit dumm ist, dann ist es eine falsche Analogie. Wenn ich in der Lage bin zu entscheiden, welchen Weg ich gehen soll, dann bin ich nicht auf halbem Weg. Es ist die Regel, dass der Supercall der erste im Konstruktor sein muss, die uns dazu verleitet, das Fenster einzuschlagen (siehe viele Beispiele des Herumwurschtelns in den Fragen und Antworten), anstatt die Tür zu benutzen. Sie stellen also alles auf den Kopf, wenn Sie versuchen, für diese Regel zu argumentieren. Die Regel muss also falsch sein.

6voto

Tip-Sy Punkte 810

Es hindert Sie also nicht daran, die Logik vor dem Aufruf von super. Es hindert Sie nur daran, Logik auszuführen, die Sie nicht in einen in einen einzigen Ausdruck passt.

Tatsächlich können Sie Logik mit mehreren Abläufen ausführen, Sie müssen Ihren Code nur in eine statische Funktion verpacken und diese in der Superanweisung aufrufen.

Anhand Ihres Beispiels:

public class MySubClassC extends MyClass {
    public MySubClassC(Object item) {
        // Create a list that contains the item, and pass the list to super
        super(createList(item));  // OK
    }

    private static List createList(item) {
        List list = new ArrayList();
        list.add(item);
        return list;
    }
}

1 Stimmen

Dies funktioniert nur, wenn der Konstruktor der Superklasse ein einzelnes, nicht-voides Argument erwartet

0 Stimmen

Anstatt sich für die super()/this() als erste Anweisung, hätten die Sprachdesigner festlegen können, dass keine Instanzmethoden oder geerbten Methoden aufgerufen werden können, bevor super()/this() . Vielleicht möchte der Auftraggeber wissen, warum es nicht auf diese Weise gemacht wurde.

5voto

Yan Chun Tang Punkte 3

Dies ist eine offizielle Wiederholung: Historisch gesehen, muss this() oder super() in einem Konstruktor an erster Stelle stehen. Dies
Die Beschränkung war nie beliebt und wurde als willkürlich empfunden. Es gab eine
eine Reihe von subtilen Gründen, darunter die Überprüfung von invokespecial,
die zu dieser Einschränkung beigetragen haben. Im Laufe der Jahre haben wir uns mit folgenden Themen befasst
auf VM-Ebene, bis zu dem Punkt, an dem es praktisch wird, diese
in Erwägung ziehen, diese Beschränkung aufzuheben, und zwar nicht nur für Aufzeichnungen, sondern für alle
Konstrukteure.

5voto

DaveFar Punkte 6589

Ich stimme voll und ganz zu, die Einschränkungen sind zu stark. Es ist nicht immer möglich, eine statische Hilfsmethode zu verwenden (wie Tom Hawtin - tackline vorgeschlagen hat) oder alle "pre-super()-Berechnungen" in einen einzigen Ausdruck im Parameter zu packen, z.B.:

class Sup {
    public Sup(final int x_) { 
        //cheap constructor 
    }
    public Sup(final Sup sup_) { 
        //expensive copy constructor 
    }
}

class Sub extends Sup {
    private int x;
    public Sub(final Sub aSub) {
        /* for aSub with aSub.x == 0, 
         * the expensive copy constructor is unnecessary:
         */

         /* if (aSub.x == 0) { 
          *    super(0);
          * } else {
          *    super(aSub);
          * } 
          * above gives error since if-construct before super() is not allowed.
          */

        /* super((aSub.x == 0) ? 0 : aSub); 
         * above gives error since the ?-operator's type is Object
         */

        super(aSub); // much slower :(  

        // further initialization of aSub
    }
}

Die Verwendung einer Ausnahme "Objekt noch nicht konstruiert", wie von Carson Myers vorgeschlagen, würde helfen, aber die Überprüfung dieser Ausnahme bei jeder Objektkonstruktion würde die Ausführung verlangsamen. Ich würde einen Java-Compiler bevorzugen, der eine bessere Differenzierung vornimmt (anstatt eine if-Anweisung zu verbieten, aber den ?-Operator innerhalb des Parameters zuzulassen), auch wenn dies die Sprachspezifikation verkompliziert.

2 Stimmen

Ich denke, die Ablehnung kommt daher, dass Sie nicht auf die Frage antworten, sondern Kommentare zum Thema abgeben. In einem Forum wäre das in Ordnung, aber SO/SE ist kein Forum :)

0 Stimmen

Hervorragendes Beispiel für die Möglichkeiten der ?: Der Typ von construct kann Sie überraschen. Ich dachte, als ich las: "Es ist nicht unmöglich --- benutze einfach eine ternäre Oper... Oh.".

3voto

Dobes Vandermeer Punkte 8123

Ich vermute, dass sie dies getan haben, um das Leben derjenigen zu erleichtern, die Tools zur Verarbeitung von Java-Code schreiben, und in geringerem Maße auch derjenigen, die Java-Code lesen.

Wenn Sie die super() o this() Aufforderung, sich zu bewegen, gibt es mehr Varianten, auf die man achten muss. Wenn Sie zum Beispiel die super() o this() Aufruf in eine bedingte if() muss es möglicherweise so intelligent sein, dass es eine implizite super() in die else . Es muss möglicherweise wissen, wie man einen Fehler meldet, wenn man super() zweimal, oder verwenden Sie super() y this() zusammen. Möglicherweise müssen Methodenaufrufe beim Empfänger untersagt werden, bis super() o this() aufgerufen wird, und herauszufinden, wann dies der Fall ist, wird kompliziert.

Dass alle diese zusätzliche Arbeit machen mussten, schien wahrscheinlich mehr Kosten als Nutzen zu bringen.

0 Stimmen

Eine vernünftige Grammatik für dieses Merkmal zu schreiben, wäre an sich schon schwierig - eine solche Grammatik würde einem Anweisungsbaum entsprechen, bei dem höchstens ein Blattknoten ein expliziter Super-Konstruktor-Aufruf ist. Ich kann mir eine Möglichkeit vorstellen, wie man sie schreiben könnte, aber mein Ansatz wäre ziemlich wahnsinnig.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X