Perché this () e super () devono essere la prima dichiarazione in un costruttore?

Java richiede che se si chiama this () o super () in un costruttore, deve essere la prima dichiarazione. Perché?

Per esempio:

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 } } 

Il compilatore Sun dice che “call to super deve essere la prima istruzione nel costruttore”. Il compilatore di Eclipse dice “La chiamata del costruttore deve essere la prima dichiarazione in un costruttore”.

Tuttavia, è ansible aggirare il problema riordinando il codice un po ‘:

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

Ecco un altro esempio:

 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 } } 

Quindi, non ti impedisce di eseguire la logica prima della chiamata a super. Ti impedisce semplicemente di eseguire una logica che non è adatta a una singola espressione.

Ci sono regole simili per chiamare this() . Il compilatore dice “la chiamata a questa deve essere la prima istruzione nel costruttore”.

Perché il compilatore ha queste restrizioni? Puoi dare un esempio di codice in cui, se il compilatore non avesse questa restrizione, accadrebbe qualcosa di brutto?

Il constructor class genitore deve essere chiamato prima del constructor della sottoclass. Ciò assicurerà che se si chiama qualsiasi metodo sulla class genitore nel costruttore, la class genitore è già stata impostata correttamente.

Quello che stai cercando di fare, passare argomenti al super costruttore è perfettamente legale, devi solo build quegli argomenti in linea come stai facendo, o passarli al tuo costruttore e poi passarli a super :

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

Se il compilatore non ha applicato questo, potresti fare questo:

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

Nei casi in cui una class parent ha un costruttore predefinito la chiamata a super viene inserita automaticamente dal compiler . Poiché ogni class in Java eredita da Object , il costruttore di oggetti deve essere chiamato in qualche modo e deve essere eseguito per primo. L’inserimento automatico di super () da parte del compilatore consente questo. L’applicazione di Super ad apparire per prima, impone che i corpi dei costruttori siano eseguiti nell’ordine corretto, ovvero: Oggetto -> Padre -> Bambino -> ChildOfChild -> SoOnSoForth

Ho trovato un modo per aggirare questo costruendo costruttori e metodi statici. Quello che volevo fare assomigliava a questo:

 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; } } 

Quindi, fondamentalmente, costruisci un object basato sui parametri del costruttore, memorizza l’object in un membro e passa anche il risultato di un metodo su quell’object nel costruttore di super. Rendere il membro definitivo era anche ragionevolmente importante in quanto la natura della class è che è immutabile. Si noti che, come accade, la costruzione di Bar richiede effettivamente alcuni oggetti intermedi, quindi non è riducibile a un solo rivestimento nel mio caso d’uso reale.

Ho finito per farlo funzionare qualcosa del genere:

 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; } } 

Codice legale e svolge il compito di eseguire più istruzioni prima di chiamare il super costruttore.

Perché la JLS dice così. Il JLS può essere modificato in modo compatibile per consentirlo? Sì. Tuttavia, complicherebbe le specifiche del linguaggio, che è già più che abbastanza complicato. Non sarebbe una cosa molto utile da fare e ci sono dei modi per aggirarla (chiama un altro costruttore con il risultato di un metodo this(fn()) – il metodo viene chiamato prima dell’altro costruttore, e quindi anche del super costruttore . Quindi il rapporto peso / potenza di fare il cambiamento è sfavorevole.

Modifica marzo 2018: nel messaggio Records: costruzione e validazione Oracle suggerisce di rimuovere questa restrizione (ma a differenza di C #, this sarà definitivamente non assegnato (DU) prima del concatenamento del costruttore).

Storicamente, this () o super () devono essere i primi in un costruttore. Questa restrizione non è mai stata popolare e percepita come arbitraria. C’erano un numero di ragioni sottili, inclusa la verifica di invokespecial, che hanno contribuito a questa restrizione. Nel corso degli anni, li abbiamo affrontati a livello di VM, fino al punto in cui diventa pratico prendere in considerazione la possibilità di rimuovere questa restrizione, non solo per i record, ma per tutti i costruttori.

Sono abbastanza sicuro (quelli che hanno familiarità con il chime di Java Specification in) che è quello di impedire a (a) di poter usare un object parzialmente costruito, e (b), forzare il costruttore della class genitrice a build su un “fresco” “object.

Alcuni esempi di una cosa “ctriggers” sarebbero:

 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 } } 

Hai chiesto perché, e le altre risposte, imo, non dico veramente perché è giusto chiamare il costruttore del tuo super, ma solo se è la prima riga. Il motivo è che non stai davvero chiamando il costruttore. In C ++, la syntax equivalente è

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

Quando vedi la clausola di inizializzazione da sola in quel modo, prima della parentesi aperta, sai che è speciale. Esegue prima delle altre esecuzioni del costruttore e, in effetti, prima che una qualsiasi delle variabili membro venga inizializzata. Non è così diverso per Java. C’è un modo per ottenere del codice (altri costruttori) da eseguire prima dell’avvio effettivo del costruttore, prima che i membri della sottoclass vengano inizializzati. E in questo modo è ansible mettere la “chiamata” (ad esempio super ) sulla prima riga. (In un certo senso, questo super o this è un po ‘prima della prima parentesi graffa aperta, anche se lo si digita dopo, perché verrà eseguito prima di arrivare al punto che tutto è completamente costruito.) Qualsiasi altro codice dopo la parentesi aperta (come int c = a + b; ) rende il compilatore dire “oh, ok, nessun altro costruttore, possiamo inizializzare tutto allora”. Quindi scappa e inizializza la tua super class e i tuoi membri e qualcos’altro, quindi avvia l’esecuzione del codice dopo la parentesi aperta.

Se, poche righe dopo, incontra del codice che dice “oh sì, quando costruisci questo object, qui ci sono i parametri che voglio che tu passi al costruttore per la class base”, è troppo tardi e non lo fa ha senso Quindi ottieni un errore del compilatore.

Semplicemente perché questa è la filosofia dell’eredità. E secondo le specifiche del linguaggio Java, questo è il modo in cui viene definito il corpo del costruttore:

ConstructorBody: {ExplicitConstructorInvocation opt optare per BlockStatements}

La prima affermazione di un corpo costruttore può essere:
-un richiamo esplicito di un altro costruttore della stessa class (utilizzando la parola chiave “this”) OR
-della superclass diretta (usando la parola chiave “super”)

Se un corpo del costruttore non inizia con una chiamata al costruttore esplicita e il costruttore dichiarato non fa parte dell’object class primordiale, il corpo del costruttore inizia implicitamente con una chiamata del costruttore della superclass “super ();”, una chiamata del costruttore di la sua superclass diretta che non accetta argomenti. E così via … ci sarà tutta una catena di costruttori chiamati fino al costruttore di Object; “Tutte le classi nella piattaforma Java sono discendenti di object”. Questa cosa si chiama ” Constructor Chaining “.

Ora, perché è questo?
E il motivo per cui Java ha definito ConstructorBody in questo modo, è che avevano bisogno di mantenere la gerarchia dell’object. Ricorda la definizione dell’eredità; Sta estendendo una class. Detto questo, non puoi estendere qualcosa che non esiste. La base (la superclass) deve essere creata per prima, quindi puoi derivarla (la sottoclass). Ecco perché li chiamavano classi Genitore e Bambino; non puoi avere un figlio senza un genitore.

A livello tecnico, una sottoclass eredita tutti i membri (campi, metodi, classi nidificate) dal suo genitore. E poiché i Costruttori NON sono membri (non appartengono agli oggetti, sono responsabili della creazione di oggetti) quindi NON sono ereditati dalle sottoclassi, ma possono essere invocati. E poiché al momento della creazione dell’object viene eseguito solo UN costruttore . Quindi, come possiamo garantire la creazione della superclass quando crei l’object sottoclass? Quindi il concetto di “concatenamento del costruttore”; quindi abbiamo la possibilità di invocare altri costruttori (cioè super) dall’interno del costruttore corrente. E Java ha richiesto che questa invocazione fosse la prima riga nel costruttore della sottoclass per mantenere la gerarchia e garantirla. Assumono che se non crei in modo esplicito l’object genitore FIRST (come se te ne fossi dimenticato), lo faranno implicitamente per te.

Questo controllo viene eseguito durante la compilazione. Ma non sono sicuro di cosa succederebbe in runtime, quale tipo di errore di runtime avremmo ottenuto, SE Java non genera un errore di compilazione quando proviamo esplicitamente a eseguire un costruttore di base all’interno di un costruttore di sottoclassi nel mezzo del suo corpo e non dalla prima riga …

Quindi, non ti impedisce di eseguire la logica prima della chiamata a super. Ti impedisce semplicemente di eseguire una logica che non è adatta a una singola espressione.

In realtà è ansible eseguire la logica con diverse expessions, devi solo avvolgere il tuo codice in una funzione statica e chiamarlo nella super dichiarazione.

Usando il tuo esempio:

 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; } } 

Sono assolutamente d’accordo, le restrizioni sono troppo forti. Utilizzare un metodo di supporto statico (come suggerito da Tom Hawtin – tackline) o spingere tutti i “calcoli pre-super ()” in una singola espressione nel parametro non è sempre ansible, ad esempio:

 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 } } 

L’utilizzo di un’eccezione “object non ancora costruito”, come suggerito da Carson Myers, sarebbe di aiuto, ma controllarlo durante la costruzione di ogni object rallenterebbe l’esecuzione. Preferirei un compilatore Java che effettui una migliore differenziazione (invece di vietare in modo irrilevante un’istruzione if, ma consentire l’operatore? All’interno del parametro), anche se questo complica le specifiche del linguaggio.

La mia ipotesi è che l’abbiano fatto per rendere la vita più facile alle persone che scrivono strumenti che elaborano il codice Java e, in misura minore, anche alle persone che stanno leggendo il codice Java.

Se permetti alla chiamata super() o this() di spostarti, ci sono più varianti da verificare. Ad esempio, se si sposta la chiamata super() o this() in un condizionale if() , potrebbe essere necessario essere abbastanza intelligente da inserire un implicito super() nel else . Potrebbe essere necessario sapere come segnalare un errore se chiami super() due volte, oppure usa super() e this() insieme. Potrebbe essere necessario disabilitare le chiamate di metodo sul ricevitore fino a quando super() o this() viene chiamato e capire quando questo diventa complicato.

Fare in modo che tutti facciano questo lavoro extra sembra probabilmente un costo maggiore del vantaggio.

È ansible utilizzare i blocchi di inizializzazione anonimi per inizializzare i campi nel figlio prima di chiamare il costruttore. Questo esempio dimostrerà:

 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"); } } 

Questo produrrà:

In genitore
Nell’inizializzatore
Nel bambino

Ho trovato un problema.

Questo non verrà compilato:

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

Questo funziona:

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

Ha senso che i costruttori completino la loro esecuzione in ordine di derivazione. Poiché una superclass non ha conoscenza di alcuna sottoclass, qualsiasi inizializzazione che deve eseguire è separata da e probabilmente prerequisito per qualsiasi inizializzazione eseguita dalla sottoclass. Pertanto, deve prima completare la sua esecuzione.

Una semplice dimostrazione:

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

L’output di questo programma è:

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

So di essere un po ‘in ritardo per la festa, ma ho usato questo trucco un paio di volte (e so che è un po’ insolito):

Creo un’interfaccia generica InfoRunnable con un metodo:

 public T run(Object... args); 

E se ho bisogno di fare qualcosa prima di passarlo al costruttore, faccio solo questo:

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

In realtà, super() è la prima affermazione di un costruttore perché per assicurarsi che la sua superclass sia completamente formata prima della costruzione della sottoclass. Anche se non hai super() nella tua prima istruzione, il compilatore lo aggiungerà per te!

TLDR:

Le altre risposte hanno affrontato il “perché” della domanda. Fornirò un trucco su questa limitazione:

L’idea di base è di dirottare la super dichiarazione con le tue dichiarazioni incorporate. Questo può essere fatto camuffando le tue affermazioni come espressioni .

Tsdr:

Considera che vogliamo fare Statement1() a Statement9() prima di chiamare super() :

 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 } } 

Il compilatore ovviamente rifiuterà il nostro codice. Quindi, invece, possiamo fare questo:

 // 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; } } 

L’unica limitazione è che la class genitore deve avere un costruttore che accetta almeno un argomento in modo da poter entrare di nascosto nella nostra dichiarazione come espressione.

Ecco un esempio più elaborato:

 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 } } 

Rielaborato in:

 // 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; } } 

In effetti, i compilatori avrebbero potuto automatizzare questo processo per noi. Avevano scelto di non farlo.

Prima di poter build un object figlio, è necessario creare l’object genitore. Come sai quando scrivi una lezione come questa:

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

si gira verso il prossimo (estensione e super sono appena nascosti):

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

Per prima cosa creiamo un Object e quindi estendiamo questo object a MyClass . Non possiamo creare MyClass prima Object . La semplice regola è che il costruttore del genitore deve essere chiamato prima del costruttore figlio. Ma sappiamo che le classi possono avere più di un costruttore. Java ci permette di scegliere un costruttore che verrà chiamato (o sarà super() o super(yourArgs...) ). Quindi, quando scrivi super(yourArgs...) ridefinisci il costruttore che verrà chiamato per creare un object genitore. Non puoi eseguire altri metodi prima di super() perché l’object non esiste ancora (ma dopo super() verrà creato un object e sarai in grado di fare tutto ciò che vuoi).

Allora, perché non possiamo eseguire this() dopo qualsiasi metodo? Come sapete this() è il costruttore della class corrente. Inoltre possiamo avere un numero diverso di costruttori nella nostra class e chiamarli così this() o this(yourArgs...) . Come ho detto, ogni costruttore ha il metodo nascosto super() . Quando scriviamo il nostro super(yourArgs...) personalizzato super(yourArgs...) rimuoviamo super() con super(yourArgs...) . Anche quando definiamo this() o this(yourArgs...) rimuoviamo anche il nostro super() nel costruttore corrente perché se super() fosse con this() nello stesso metodo, creerebbe più di un object genitore. That is why the same rules imposed for this() method. It just retransmits parent object creation to another child constructor and that constructor calls super() constructor for parent creation. So, the code will be like this in fact:

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

As others say you can execute code like this:

 this(a+b); 

also you can execute code like this:

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

But you can’t execute code like this because your method doesn’t exists yet:

 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; } } 

Also you are obliged to have super() constructor in your chain of this() methods. You can’t have an object creation like this:

 public MyClass{ public MyClass(int a) { this(a, 5); } public MyClass(int a, int b) { this(a); } } 
 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); } } 

See the example if we are calling the constructor C(int x) then value of z is depend on y if we do not call C() in the first line then it will be the problem for z. z would not be able to get correct value.

Can you give a code example where, if the compiler did not have this restriction, something bad would happen?

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

An exception during construction almost always indicates that the object being constructed could not be properly initialized, now is in a bad state, unusable, and must be garbage collected. However, a constructor of a subclass has got the ability to ignore an exception occurred in one of its superclasss and to return a partially initialized object. In the above example, if the argument given to new Bad() is either 0 or greater than 100, then neither essential1 nor essential2 are properly initialized.

You may say that ignoring exceptions is always a bad idea. OK, here’s another example:

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

Funny, isn't it? How many objects are we creating in this example? One? Two? Or maybe nothing...

Allowing to call super() or this() in the middle of a constructor would open a Pandora's box of heinous constructors.


On the other hand, I understand a frequent need to include some static part before a call to super() or this() . This might be any code not relying on this reference (which, in fact, already exists at the very beginning of a constructor, but cannot be used orderly until super() or this() returns) and needed to make such call. In addition, like in any method, there's a chance that some local variables created before the call to super() or this() will be needed after it.

In such cases, you have the following opportunities:

  1. Use the pattern presented at this answer , which allows to circumvent the restriction.
  2. Wait for the Java team to allow pre- super() and pre- this() code. It may be done by imposing a restriction on where super() or this() may occur in a constructor. Actually, even today's compiler is able to distinguish good and bad (or potentially bad) cases with the degree enough to securely allow static code addition at the beginning of a constructor. Indeed, assume that super() and this() return this reference and, in turn, your constructor has
 return this; 

at the end. As well as the compiler rejects the code

 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; } 

with the error "variable x might not have been initialized", it could do so on this variable, making its checks on it just like on any other local variable. The only difference is this cannot be assigned by any means other than super() or this() call (and, as usual, if there is no such call at a constructor, super() is implicitly inserted by compiler in the beginning) and might not be assigned twice. In case of any doubt (like in the first get() , where x is actually always assigned), the compiler could return an error. That would be better than simply return error on any constructor where there is something except a comment before super() or this() .