725 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.

60 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.

3voto

Jaydev Punkte 1673

Es ist sinnvoll, dass die Konstruktoren ihre Ausführung in der Reihenfolge von Ableitung. Da eine Oberklasse keine Kenntnis von einer Unterklasse hat, ist jede Initialisierung, die sie durchführen muss, getrennt von und möglicherweise Voraussetzung für jede Initialisierung, die von der Unterklasse durchgeführt wird. Daher muss sie ihre Ausführung zuerst abschließen.

Eine einfache Demonstration:

class A {
    A() {
        System.out.println("Inside A's constructor.");
    }
}

class B extends A {
    B() {
        System.out.println("Inside B's constructor.");
    }
}

class C extends B {
    C() {
        System.out.println("Inside C's constructor.");
    }
}

class CallingCons {
    public static void main(String args[]) {
        C c = new C();
    }
}

Die Ausgabe dieses Programms lautet:

Inside A's constructor
Inside B's constructor
Inside C's constructor

0 Stimmen

In diesem Beispiel gibt es in jeder Klasse einen Standardkonstruktor, so dass die Methode super(...,...) in der Unterklasse nicht unbedingt aufgerufen werden muss

3voto

Vouze Punkte 1598

Ich habe eine Abhilfe gefunden.

Das lässt sich nicht kompilieren:

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

Das funktioniert:

public class MySubClass extends MyClass {
    public MySubClass(int a, int b) {
        this(a + b);
        doSomething2(a);
        doSomething3(b);
    }

    private MySubClass(int c) {
        super(c);
        doSomething(c);
    }
}

4 Stimmen

Die Frage bezieht sich nicht auf eine Umgehungslösung. Tatsächlich findet sich eine Abhilfe in der Frage selbst.

0 Stimmen

Dies ist keine Umgehungslösung. Sie können immer noch nicht mehrere Codezeilen schreiben.

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.

2voto

Savvas Dalkitsis Punkte 11241

Sie können anonyme Initialisierungsblöcke verwenden, um Felder im Kind zu initialisieren, bevor Sie dessen Konstruktor aufrufen. Dieses Beispiel zeigt:

public class Test {
    public static void main(String[] args) {
        new Child();
    }
}

class Parent {
    public Parent() {
        System.out.println("In parent");
    }
}

class Child extends Parent {

    {
        System.out.println("In initializer");
    }

    public Child() {
        super();
        System.out.println("In child");
    }
}

Dies führt zur Ausgabe von :

Bei Eltern
Im Initialisierer
Im Kind

3 Stimmen

Dies bringt jedoch keinen zusätzlichen Nutzen gegenüber dem Hinzufügen der System.out.println("In initializer") als erste Zeile nach "super()", oder? Was nützlich wäre, wäre eine Möglichkeit, Code auszuführen vor die Elternteil aufgebaut ist.

0 Stimmen

In der Tat. Wenn Sie versuchen, etwas hinzuzufügen, müssen Sie den berechneten Zustand irgendwo speichern. Selbst wenn der Compiler dies zulässt, wie wird der temporäre Speicher aussehen? Ein weiteres Feld nur für die Initialisierung zuweisen? Aber das ist Speicherverschwendung.

2 Stimmen

Dies ist falsch. Instanzinitialisierungen werden nach der Rückkehr des übergeordneten Konstruktoraufrufs eingefügt.

1voto

mid Punkte 410

Ich weiß, dass ich ein bisschen spät dran bin, aber ich habe diesen Trick schon ein paar Mal angewandt (und ich weiß, dass er etwas ungewöhnlich ist):

Ich erstelle eine generische Schnittstelle InfoRunnable<T> mit einer Methode:

public T run(Object... args);

Und wenn ich etwas tun muss, bevor ich es an den Konstruktor übergebe, mache ich einfach dies:

super(new InfoRunnable<ThingToPass>() {
    public ThingToPass run(Object... args) {
        /* do your things here */
    }
}.run(/* args here */));

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