wake-up-neo.com

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

Java erfordert, dass, wenn Sie this () oder super () in einem Konstruktor aufrufen, es 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 "Aufruf an Super muss erste Anweisung im Konstruktor sein". Der Eclipse-Compiler sagt "Konstruktoraufruf muss die erste Anweisung in einem Konstruktor sein".

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

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

Hier ist 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 was Sie nicht davon abhält, Logik auszuführen vor dem Aufruf von super. Es hindert Sie nur daran, Logik auszuführen, die Sie nicht in einen einzelnen Ausdruck einpassen können.

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

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

559
Joe Daley

Die übergeordnete Klasse 'constructor muss vor der Unterklasse' constructor aufgerufen werden. Dadurch wird sichergestellt, dass die übergeordnete Klasse bereits ordnungsgemäß eingerichtet wurde, wenn Sie Methoden für die übergeordnete Klasse in Ihrem Konstruktor aufrufen.

Was Sie versuchen, Argumente an den Superkonstruktor zu übergeben, ist völlig legal. Sie müssen diese Argumente nur inline konstruieren oder an Ihren Konstruktor übergeben und sie dann an super übergeben:

public MySubClassB extends MyClass {
        public MySubClassB(Object[] myArray) {
                super(myArray);
        }
}

Wenn der Compiler dies nicht erzwungen hat, können Sie dies tun:

public MySubClassB extends MyClass {
        public MySubClassB(Object[] myArray) {
                someMethodOnSuper(); //ERROR super not yet constructed
                super(myArray);
        }
}

In Fällen, in denen eine parent -Klasse einen Standardkonstruktor hat, wird der Aufruf von super für Sie automatisch vom compiler eingefügt. Da jede Klasse in Java von Object erbt, muss der Objektkonstruktor irgendwie aufgerufen und zuerst ausgeführt werden. Das automatische Einfügen von super () durch den Compiler ermöglicht dies super, um zuerst zu erscheinen, erzwingt, dass Konstruktorkörper in der richtigen Reihenfolge ausgeführt werden: Object -> Parent -> Child -> ChildOfChild -> SoOnSoForth

174
anio

Ich habe einen Weg gefunden, dies zu umgehen, indem ich Konstruktoren und statische Methoden verkettet habe. Was ich machen wollte sah ungefähr so ​​aus:

public class Foo extends Baz {
  private final Bar myBar;

  public Foo(String arg1, String arg2) {
    // ...
    // ... Some other stuff needed to construct a 'Bar'...
    // ...
    final Bar b = new Bar(arg1, arg2);
    super(b.baz()):
    myBar = b;
  }
}

Erstellen Sie also im Grunde ein Objekt auf der Grundlage von Konstruktorparametern, speichern Sie das Objekt in einem Member und übergeben Sie das Ergebnis einer Methode für dieses Objekt an den Konstruktor von super. Es war auch einigermaßen wichtig, das Finale des Mitglieds zu erreichen, da die Art der Klasse darin besteht, dass sie unveränderlich ist. Beachten Sie, dass das Konstruieren von Bar tatsächlich einige Zwischenobjekte erfordert, sodass es in meinem tatsächlichen Anwendungsfall nicht auf einen Einzeiler reduziert werden kann.

Am Ende habe ich es so aussehen lassen:

public class Foo extends Baz {
  private final Bar myBar;

  private static Bar makeBar(String arg1,  String arg2) {
    // My more complicated setup routine to actually make 'Bar' goes here...
    return new Bar(arg1, arg2);
  }

  public Foo(String arg1, String arg2) {
    this(makeBar(arg1, arg2));
  }

  private Foo(Bar bar) {
    super(bar.baz());
    myBar = bar;
  }
}

Gesetzlicher Code, der die Aufgabe erfüllt, mehrere Anweisungen auszuführen, bevor der Superkonstruktor aufgerufen wird.

93
pendor

Weil die JLS es so sagt. Konnte das JLS in einer kompatiblen Weise geändert werden, um es zu ermöglichen? Yup.

Dies würde jedoch die Sprachspezifikation erschweren, die bereits mehr als kompliziert genug ist. Es wäre nicht sehr nützlich, dies zu tun, und es gibt Möglichkeiten, dies zu umgehen (rufen Sie einen anderen Konstruktor mit dem Ergebnis einer statischen Methode oder eines Lambda-Ausdrucks auf this(fn()) - die Methode wird vor dem anderen Konstruktor aufgerufen, und daher auch der super Konstruktor). Das Verhältnis von Leistung zu Gewicht ist daher ungünstig.

Beachten Sie, dass diese Regel allein die Verwendung von Feldern nicht verhindert, bevor die Superklasse die Konstruktion abgeschlossen hat.

Betrachten Sie diese illegalen Beispiele.

super(this.x = 5);

super(this.fn());

super(fn());

super(x);

super(this instanceof SubClass);
// this.getClass() would be /really/ useful sometimes.

Dieses Beispiel ist legal, aber "falsch".

class MyBase {
    MyBase() {
        fn();
    }
    abstract void fn();
}
class MyDerived extends MyBase {
    void fn() {
       // ???
    }
}

Wenn im obigen Beispiel MyDerived.fn erforderte Argumente vom Konstruktor MyDerived, durch die sie mit einem ThreadLocal verschoben werden mussten. ; (

Übrigens wird seit Java 1.4 das synthetische Feld, das den äußeren this enthält, zugewiesen, bevor der Superkonstruktor für innere Klassen aufgerufen wird. Dies verursachte spezielle NullPointerException -Ereignisse im Code Kompiliert für frühere Versionen.

Beachten Sie außerdem, dass bei Vorhandensein einer unsicheren Veröffentlichung die Konstruktion von anderen Threads in einer anderen Reihenfolge angezeigt werden kann, sofern keine Vorsichtsmaßnahmen getroffen werden.

Edit March 2018: In message Records: construction and validation schlägt Oracle vor, diese Einschränkung aufzuheben (aber im Gegensatz zu C #, this wird definitiv nicht zugewiesen (DU) bevor der Konstruktor verkettet wird).

Historisch gesehen muss this () oder super () in einem Konstruktor an erster Stelle stehen. Diese Einschränkung war nie populär und wurde als willkürlich empfunden. Es gab eine Reihe subtiler Gründe, einschließlich der Überprüfung von invokespecial, die zu dieser Einschränkung beitrugen. Im Laufe der Jahre haben wir diese Probleme auf der Ebene VM) behoben, bis es praktisch wird, diese Einschränkung nicht nur für Datensätze, sondern für alle Konstruktoren aufzuheben.

44

Ich bin mir ziemlich sicher (diejenigen, die mit dem Java= Specification chime in) vertraut sind), dass dies verhindern soll, dass Sie (a) ein teilweise konstruiertes Objekt verwenden dürfen und (b) das erzwingen Konstruktor der übergeordneten Klasse zum Konstruieren eines "frischen" Objekts.

Einige Beispiele für eine "schlechte" Sache wären:

class Thing
{
    final int x;
    Thing(int x) { this.x = x; }
}

class Bad1 extends Thing
{
    final int z;
    Bad1(int x, int y)
    {
        this.z = this.x + this.y; // WHOOPS! x hasn't been set yet
        super(x);
    }        
}

class Bad2 extends Thing
{
    final int y;
    Bad2(int x, int y)
    {
        this.x = 33;
        this.y = y; 
        super(x); // WHOOPS! x is supposed to be final
    }        
}
13
Jason S

Einfach, weil dies die Vererbungsphilosophie ist. Und gemäß der Sprachspezifikation Java) wird der Rumpf des Konstruktors folgendermaßen definiert:

ConstructorBody: {ExplicitConstructorInvocationopt BlockStatementsopt }

Die erste Anweisung eines Konstruktorkörpers kann beides sein

  • ein expliziter Aufruf eines anderen Konstruktors derselben Klasse (unter Verwendung des Schlüsselworts "this"); oder
  • ein expliziter Aufruf der direkten Superklasse (mit dem Schlüsselwort "super")

Wenn ein Konstruktorkörper nicht mit einem expliziten Konstruktoraufruf beginnt und der deklarierte Konstruktor nicht Teil der primordialen Klasse Object ist, beginnt der Konstruktorkörper implizit mit einem Superklassenkonstruktoraufruf "super ();", einem Aufruf des Konstruktors von seine direkte Superklasse, die keine Argumente akzeptiert. Und so weiter. Es wird eine ganze Reihe von Konstruktoren geben, die bis zum Konstruktor von Object zurückkehren. "Alle Klassen in der Java Plattform sind Nachkommen von Object". Dieses Ding heißt "Constructor Chaining".

Warum ist das nun so?
Und der Grund, warum Java) den ConstructorBody auf diese Weise definiert hat, ist, dass sie es mussten pflegen Sie die Hierarchie des Objekts. Erinnern Sie sich an die Definition der Vererbung; Es erweitert eine Klasse. Wenn dies gesagt ist, können Sie etwas, das nicht existiert, nicht erweitern. Die Basis (die Oberklasse) muss zuerst erstellt werden, dann können Sie sie ableiten (die Unterklasse). Deshalb nannten sie sie Eltern- und Kinderklassen; Sie können kein Kind ohne Eltern haben.

Auf technischer Ebene erbt eine Unterklasse alle Mitglieder (Felder, Methoden, verschachtelte Klassen) von ihrem übergeordneten Element. Und da Konstruktoren KEINE Mitglieder sind (sie gehören nicht zu Objekten. Sie sind für das Erstellen von Objekten verantwortlich), werden sie NICHT von Unterklassen geerbt, sondern können aufgerufen werden. Und seit zum Zeitpunkt der Objekterstellung wird nur EIN Konstruktor ausgeführt. Wie können wir also die Erstellung der Oberklasse beim Erstellen des Unterklassenobjekts gewährleisten? So das Konzept der "Konstruktorkettung"; Wir haben also die Möglichkeit, andere Konstruktoren (d. h. Super) aus dem aktuellen Konstruktor heraus aufzurufen. Und Java erforderte, dass dieser Aufruf die FIRST-Zeile im Unterklassenkonstruktor ist, um die Hierarchie aufrechtzuerhalten und zu gewährleisten. Sie gehen davon aus, dass Sie das übergeordnete Objekt FIRST nicht explizit erstellen (wie wenn Sie es vergessen haben) darüber), werden sie es implizit für Sie tun.

Diese Prüfung erfolgt beim Übersetzen. Aber ich bin nicht sicher, was zur Laufzeit passieren würde, welche Art von Laufzeitfehler wir bekommen würden, IF Java löst keinen Kompilierungsfehler aus, wenn wir explizit versuchen, einen Basiskonstruktor auszuführen innerhalb des Konstruktors einer Unterklasse in der Mitte ihres Körpers und nicht von der ersten Zeile an ...

12
Randa Sbeity

Sie haben gefragt, warum, und die anderen Antworten, imo, sagen nicht wirklich, warum es in Ordnung ist, den Konstruktor Ihres Supers aufzurufen, aber nur, wenn es die allererste Zeile ist. Der Grund ist, dass Sie nicht wirklich aufrufend der Konstruktor sind. In C++ lautet die entsprechende Syntax

MySubClass: MyClass {

public:

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

};

Wenn Sie die Initialisierungsklausel allein so sehen, wissen Sie, dass es etwas Besonderes ist, bevor die geschweifte Klammer geöffnet wird. Es wird ausgeführt, bevor der Rest des Konstruktors ausgeführt wird, und tatsächlich bevor eine der Membervariablen initialisiert wird. Bei Java ist das nicht anders. Es gibt eine Möglichkeit, Code (andere Konstruktoren) auszuführen, bevor der Konstruktor wirklich gestartet wird, bevor Mitglieder der Unterklasse initialisiert werden. Und auf diese Weise setzen Sie den "Aufruf" (zB super) in die allererste Zeile. (In gewisser Weise steht super oder this vor der ersten offenen Klammer, auch wenn Sie sie danach eingeben, da sie ausgeführt wird, bevor Sie den Punkt erreichen, an dem alles vollständig ist konstruiert.) Jeder andere Code nach der offenen Klammer (wie int c = a + b;) lässt den Compiler sagen "oh, ok, keine anderen Konstruktoren, wir können dann alles initialisieren." Also rennt es davon und initialisiert Ihre Superklasse und Ihre Mitglieder und so weiter und startet dann die Ausführung des Codes nach der offenen Klammer.

Wenn ein paar Zeilen später ein Code mit der Aufschrift "Oh ja, wenn Sie dieses Objekt erstellen, sollten Sie die folgenden Parameter an den Konstruktor für die Basisklasse weitergeben" angezeigt wird, ist es zu spät und nicht Sinn machen. Sie erhalten also einen Compilerfehler.

9
Kate Gregory

Es hindert Sie also nicht daran, Logik vor dem Aufruf von super auszuführen. Es hindert Sie nur daran, Logik auszuführen, die Sie nicht in einen einzelnen Ausdruck einpassen können.

Tatsächlich können Sie Logik mit mehreren Expessionen ausführen. Sie müssen lediglich Ihren Code in eine statische Funktion einschließen und sie in der Super-Anweisung aufrufen.

Verwenden Sie Ihr Beispiel:

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;
    }
}
6
Tip-Sy

Ich stimme voll und ganz zu, die Einschränkungen sind zu stark. Die Verwendung einer statischen Hilfsmethode (wie von Tom Hawtin vorgeschlagen - Tackline) oder das Verschieben aller "Pre-Super () - Berechnungen" in einen einzelnen Ausdruck im Parameter ist nicht immer möglich, z.

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 erstellt", wie Carson Myers vorgeschlagen hat, würde helfen, aber die Überprüfung dieser Ausnahme während jeder Objekterstellung würde die Ausführung verlangsamen. Ich würde einen Java) - Compiler bevorzugen, der eine bessere Unterscheidung macht (anstatt unwillkürlich eine if-Anweisung zu verbieten, aber den? -Operator innerhalb des Parameters zuzulassen), selbst wenn dies die Sprachspezifikation kompliziert.

4
DaveFar

Ich habe eine Lösung gefunden.

Dies wird nicht kompiliert:

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);
    }
}
3
Vouze

Können Sie ein Codebeispiel geben, in dem, wenn der Compiler diese Einschränkung nicht hätte, etwas Schlimmes passieren würde?

class Good {
    int essential1;
    int essential2;

    Good(int n) {
        if (n > 100)
            throw new IllegalArgumentException("n is too large!");
        essential1 = 1 / n;
        essential2 = n + 2;
    }
}

class Bad extends Good {
    Bad(int n) {
        try {
            super(n);
        } catch (Exception e) {
            // Exception is ignored
        }
    }

    public static void main(String[] args) {
        Bad b = new Bad(0);
//        b = new Bad(101);
        System.out.println(b.essential1 + b.essential2);
    }
}

Eine Ausnahme während der Erstellung weist fast immer darauf hin, dass das zu erstellende Objekt nicht ordnungsgemäß initialisiert werden konnte, sich jetzt in einem fehlerhaften Zustand befindet, unbrauchbar ist und dass Müll gesammelt werden muss. Ein Konstruktor einer Unterklasse hat jedoch die Möglichkeit, eine in einer seiner Oberklassen aufgetretene Ausnahme zu ignorieren und ein teilweise initialisiertes Objekt zurückzugeben. Wenn im obigen Beispiel das Argument für new Bad() entweder 0 oder größer als 100 ist, werden weder essential1 Noch essential2 Ordnungsgemäß initialisiert.

Sie können sagen, dass das Ignorieren von Ausnahmen immer eine schlechte Idee ist. OK, hier ist ein weiteres Beispiel:

class Bad extends Good {
    Bad(int n) {
        for (int i = 0; i < n; i++)
            super(i);
    }
}

Komisch, nicht wahr? Wie viele Objekte erstellen wir in diesem Beispiel? Ein? Zwei? Oder vielleicht gar nichts ...

Das Aufrufen von super() oder this() in der Mitte eines Konstruktors würde die Büchse einer Pandora mit abscheulichen Konstruktoren öffnen.


Andererseits verstehe ich die häufige Notwendigkeit, einen statischen Teil vor einem Aufruf von super() oder this() einzuschließen. Dies kann jeder Code sein, der nicht auf einer this -Referenz basiert (die tatsächlich bereits ganz am Anfang eines Konstruktors vorhanden ist, aber nicht ordnungsgemäß verwendet werden kann, bis super() oder this() kehrt zurück) und musste einen solchen Anruf tätigen. Wie bei jeder Methode besteht außerdem die Möglichkeit, dass einige lokale Variablen, die vor dem Aufruf von super() oder this() erstellt wurden, danach benötigt werden.

In solchen Fällen haben Sie folgende Möglichkeiten:

  1. Verwenden Sie das Muster unter diese Antwort , um die Einschränkung zu umgehen.
  2. Warten Sie, bis das Java) - Team den Code presuper() und prethis() zugelassen hat. Dies kann durch Auferlegung einer Einschränkung für where super() oder this() können in einem Konstruktor vorkommen. Tatsächlich kann sogar der heutige Compiler gute und schlechte (oder potenziell schlechte) Fälle mit dem Grad unterscheiden, der das sichere Hinzufügen von statischem Code zu Beginn von zulässt Ein Konstruktor: Nehmen Sie in der Tat an, dass super() und this() die Referenz this zurückgeben und Ihr Konstruktor wiederum hat
return this;

am Ende. Ebenso lehnt der Compiler den Code ab

public int get() {
    int x;
    for (int i = 0; i < 10; i++)
        x = i;
    return x;
}

public int get(int y) {
    int x;
    if (y > 0)
        x = y;
    return x;
}

public int get(boolean b) {
    int x;
    try {
        x = 1;
    } catch (Exception e) {
    }
    return x;
}

wenn der Fehler "Variable x wurde möglicherweise nicht initialisiert" auftritt, kann dies für this -Variable durchgeführt werden, wobei die Überprüfung genau wie für jede andere lokale Variable erfolgt. Der einzige Unterschied besteht darin, dass this nur durch super() oder this() zugewiesen werden kann (und, wie üblich, wenn bei einem Konstruktor kein solcher Aufruf vorhanden ist, super() wird vom Compiler implizit am Anfang eingefügt und darf nicht doppelt vergeben werden. Im Zweifelsfall (wie in der ersten get(), in der x eigentlich immer zugewiesen ist) kann der Compiler einen Fehler zurückgeben. Das wäre besser, als einfach einen Fehler in einem Konstruktor zurückzugeben, in dem vor super() oder this() etwas anderes als ein Kommentar steht.

3
John McClane

Meine Vermutung ist, dass sie dies getan haben, um das Leben von Leuten zu erleichtern, die Werkzeuge schreiben, die Java code verarbeiten, und in geringerem Maße auch von Leuten, die Java code lesen.

Wenn Sie zulassen, dass sich der Aufruf super() oder this() bewegt, müssen Sie nach weiteren Variationen suchen. Wenn Sie beispielsweise den Aufruf super() oder this() in eine bedingte if() verschieben, muss er möglicherweise intelligent genug sein, um eine implizite super() einzufügen. in die else. Möglicherweise müssen Sie wissen, wie Sie einen Fehler melden können, wenn Sie super() zweimal aufrufen oder super() und this() zusammen verwenden. Möglicherweise müssen Methodenaufrufe für den Empfänger gesperrt werden, bis super() oder this() aufgerufen wird, und es muss herausgefunden werden, wann dies kompliziert wird.

Alle dazu zu bringen, diese zusätzliche Arbeit zu erledigen, schien wahrscheinlich mehr Kosten als Nutzen zu sein.

3

Sie können anonyme Initialisierungsblöcke verwenden, um Felder im untergeordneten Element 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 wird Folgendes ausgeben:

Im Elternteil
In der Initialisierung
Im Kind

2

Es ist sinnvoll, dass Konstruktoren ihre Ausführung in der Reihenfolge der Ableitung abschließen. Da eine Superklasse keine Unterklasse kennt, ist jede Initialisierung, die sie durchführen muss, unabhängig von einer Initialisierung, die von der Unterklasse durchgeführt wird, und möglicherweise eine Voraussetzung dafür. Daher muss zuerst die Ausführung abgeschlossen werden.

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 ist:

Inside A's constructor
Inside B's constructor
Inside C's constructor
2
Jaydev

Tatsächlich ist super() die erste Anweisung eines Konstruktors, um sicherzustellen, dass die Oberklasse vollständig gebildet ist, bevor die Unterklasse erstellt wird. Auch wenn Sie in Ihrer ersten Anweisung kein super() haben, wird der Compiler es für Sie hinzufügen!

Das liegt daran, dass Ihr Konstruktor von anderen Konstruktoren abhängig ist. Damit Ihr Konstruktor korrekt arbeitet, ist es notwendig, dass andere Konstruktoren korrekt arbeiten, was abhängig ist. Aus diesem Grund müssen Sie zuerst die abhängigen Konstruktoren überprüfen, die entweder this () oder super () in Ihrem Konstruktor aufgerufen haben. Wenn andere Konstruktoren, die entweder this () oder super () aufrufen, ein Problem haben, führen Sie andere Anweisungen aus, da alle fehlschlagen, wenn der aufgerufene Konstruktor fehlschlägt.

1
Farrukh Ahmed

Ich weiß, dass ich ein bisschen zu spät zur Party komme, aber ich habe diesen Trick ein paar Mal angewendet (und ich weiß, dass es ein bisschen 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 weitergebe, mache ich einfach Folgendes:

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

Tldr:

Die anderen Antworten haben sich mit dem "Warum" der Frage befasst. Ich werde ein Hack um diese Einschränkung herum geben:

Die Grundidee ist Hijack die super Anweisung mit Ihren eingebetteten Anweisungen. Dies kann durch Verschleiern Ihrer Aussagen als Ausdrücke geschehen.

Tsdr:

Stellen Sie sich vor, wir möchten Statement1() bis Statement9() ausführen, bevor wir super() aufrufen:

public class Child extends Parent {
    public Child(T1 _1, T2 _2, T3 _3) {
        Statement_1();
        Statement_2();
        Statement_3(); // and etc...
        Statement_9();
        super(_1, _2, _3); // compiler rejects because this is not the first line
    }
}

Der Compiler wird unseren Code natürlich ablehnen. Also können wir stattdessen Folgendes tun:

// This compiles fine:

public class Child extends Parent {
    public Child(T1 _1, T2 _2, T3 _3) {
        super(F(_1), _2, _3);
    }

    public static T1 F(T1 _1) {
        Statement_1();
        Statement_2();
        Statement_3(); // and etc...
        Statement_9();
        return _1;
    }
}

Die einzige Einschränkung besteht darin, dass die übergeordnete Klasse einen Konstruktor haben muss, der mindestens ein Argument aufnimmt, damit wir unsere Anweisung als Ausdruck einschleichen können.

Hier ist ein ausführlicheres Beispiel:

public class Child extends Parent {
    public Child(int i, String s, T1 t1) {
        i = i * 10 - 123;
        if (s.length() > i) {
            s = "This is substr s: " + s.substring(0, 5);
        } else {
            s = "Asdfg";
        }
        t1.Set(i);
        T2 t2 = t1.Get();
        t2.F();
        Object obj = Static_Class.A_Static_Method(i, s, t1);
        super(obj, i, "some argument", s, t1, t2); // compiler rejects because this is not the first line
    }
}

Überarbeitet zu:

// This compiles fine:

public class Child extends Parent {
    public Child(int i, String s, T1 t1) {
        super(Arg1(i, s, t1), Arg2(i), "some argument", Arg4(i, s), t1, Arg6(i, t1));
    }

    private static Object Arg1(int i, String s, T1 t1) {
        i = Arg2(i);
        s = Arg4(s);
        return Static_Class.A_Static_Method(i, s, t1);
    }

    private static int Arg2(int i) {
        i = i * 10 - 123;
        return i;
    }

    private static String Arg4(int i, String s) {
        i = Arg2(i);
        if (s.length() > i) {
            s = "This is sub s: " + s.substring(0, 5);
        } else {
            s = "Asdfg";
        }
        return s;
    }

    private static T2 Arg6(int i, T1 t1) {
        i = Arg2(i);
        t1.Set(i);
        T2 t2 = t1.Get();
        t2.F();
        return t2;
    }
}

Tatsächlich hätten Compiler diesen Prozess für uns automatisieren können. Sie hatten nur beschlossen, es nicht zu tun.

0
Pacerier

Bevor Sie ein untergeordnetes Objekt erstellen können, muss das übergeordnete Objekt erstellt werden. Wie Sie wissen, wenn Sie eine Klasse wie diese schreiben:

public MyClass {
        public MyClass(String someArg) {
                System.out.println(someArg);
        }
}

es dreht sich um die nächste (verlängern und super sind nur versteckt):

public MyClass extends Object{
        public MyClass(String someArg) {
                super();
                System.out.println(someArg);
        }
}

Zuerst erstellen wir ein Object und erweitern dieses Objekt dann auf MyClass. Wir können MyClass nicht vor dem Object erstellen. Die einfache Regel lautet, dass der Konstruktor des übergeordneten Elements vor dem untergeordneten Konstruktor aufgerufen werden muss. Wir wissen jedoch, dass Klassen mehr als einen Konstruktor haben können. Java erlauben uns, einen Konstruktor auszuwählen, der aufgerufen wird (entweder super() oder super(yourArgs...)). Wenn Sie also super(yourArgs...) schreiben, ] Sie definieren den Konstruktor neu, der aufgerufen wird, um ein übergeordnetes Objekt zu erstellen. Sie können keine anderen Methoden vor super() ausführen, da das Objekt noch nicht vorhanden ist (aber nach super() wird ein Objekt erstellt und Sie können alles tun, was Sie wollen).

Warum können wir dann nach keiner Methode this() ausführen? Wie Sie wissen, ist this() der Konstruktor der aktuellen Klasse. Wir können auch eine unterschiedliche Anzahl von Konstruktoren in unserer Klasse haben und diese wie this() oder this(yourArgs...) aufrufen. Wie gesagt, jeder Konstruktor hat eine versteckte Methode super(). Wenn wir unser benutzerdefiniertes super(yourArgs...) schreiben, entfernen wir super() mit super(yourArgs...). Auch wenn wir this() oder this(yourArgs...) definieren, entfernen wir auch unser super() im aktuellen Konstruktor, da wenn super() mit this() in der gleichen Methode wäre würde es mehr als ein übergeordnetes Objekt erstellen. Aus diesem Grund gelten für die Methode this() dieselben Regeln. Die Erstellung des übergeordneten Objekts wird nur an einen anderen untergeordneten Konstruktor weitergeleitet, und dieser Konstruktor ruft den Konstruktor super() für die Erstellung des übergeordneten Objekts auf. Der Code sieht also tatsächlich so aus:

public MyClass extends Object{
        public MyClass(int a) {
                super();
                System.out.println(a);
        }
        public MyClass(int a, int b) {
                this(a);
                System.out.println(b);
        }
}

Wie andere sagen, können Sie Code wie folgt ausführen:

this(a+b);

sie können auch folgenden Code ausführen:

public MyClass(int a, SomeObject someObject) {
    this(someObject.add(a+5));
}

Sie können jedoch keinen Code wie diesen ausführen, da Ihre Methode noch nicht existiert:

public MyClass extends Object{
    public MyClass(int a) {

    }
    public MyClass(int a, int b) {
        this(add(a, b));
    }
    public int add(int a, int b){
        return a+b;
    }
}

Außerdem müssen Sie den Konstruktor super() in Ihrer Kette von this() - Methoden haben. Sie können keine Objekterstellung wie diese haben:

public MyClass{
        public MyClass(int a) {
                this(a, 5);
        }
        public MyClass(int a, int b) {
                this(a);
        }
}
0
Alexandr
class C
{
    int y,z;

    C()
    {
        y=10;
    }

    C(int x)
    {
        C();
        z=x+y;
        System.out.println(z);
    }
}

class A
{
    public static void main(String a[])
    {
        new C(10);
    }
}

Sehen Sie sich das Beispiel an, wenn wir den Konstruktor C(int x) aufrufen, dann hängt der Wert von z von y ab. Wenn wir C() nicht in der ersten Zeile aufrufen, ist dies das Problem für z. z könnte keinen korrekten Wert erhalten.

0
PANKAJ KUMAR