Por que isso () e super () tem que ser a primeira instrução em um construtor?

Java requer que, se você chamar this () ou super () em um construtor, ele deve ser a primeira instrução. Por quê?

Por exemplo:

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

O compilador Sun diz que “call to super deve ser a primeira instrução no construtor”. O compilador do Eclipse diz “A chamada do construtor deve ser a primeira instrução em um construtor”.

No entanto, você pode contornar isso reorganizando o código um pouco:

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

Aqui está outro exemplo:

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

Então, não está impedindo você de executar a lógica antes da chamada para o super. É apenas impedi-lo de executar a lógica que você não pode se encheckboxr em uma única expressão.

Existem regras semelhantes para chamar this() . O compilador diz “chamar para isso deve ser a primeira instrução no construtor”.

Por que o compilador tem essas restrições? Você pode dar um exemplo de código onde, se o compilador não tivesse essa restrição, algo ruim aconteceria?

O constructor class pai precisa ser chamado antes do constructor da subclass. Isso garantirá que, se você chamar qualquer método na class pai em seu construtor, a class pai já tenha sido configurada corretamente.

O que você está tentando fazer, passar args para o super construtor é perfeitamente legal, você só precisa construir esses argumentos em linha como você está fazendo, ou passá-los para o seu construtor e então passá-los para o super :

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

Se o compilador não impusesse isso, você poderia fazer isso:

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

Nos casos em que uma class parent possui um construtor padrão, a chamada para super é inserida automaticamente para você pelo compiler . Como cada class em Java é herdada de Object , o construtor de objects deve ser chamado de alguma forma e deve ser executado primeiro. A inserção automática de super () pelo compilador permite isso. Reforçar o super a aparecer primeiro impõe que os corpos do construtor sejam executados na ordem correta, que seria: Object -> Parent -> Child -> ChildOfChild -> SoOnSoForth

Eu encontrei uma maneira de contornar isso encadeando construtores e methods estáticos. O que eu queria fazer era algo assim:

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

Basicamente, construa um object baseado em parâmetros de construtor, armazene o object em um membro e também passe o resultado de um método naquele object para o construtor de super. Tornar o membro final também foi razoavelmente importante, já que a natureza da class é que é imutável. Note que, quando isso acontece, a construção de Bar na verdade leva alguns objects intermediários, portanto, ele não é redutível a um one-liner no meu caso de uso real.

Eu acabei fazendo funcionar algo assim:

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

Código legal, e realiza a tarefa de executar múltiplas instruções antes de chamar o super construtor.

Porque o JLS diz isso. O JLS poderia ser alterado de maneira compatível para permitir isso? Sim. No entanto, isso complicaria a especificação da linguagem, que já é mais do que complicada o suficiente. Não seria uma coisa muito útil para fazer e há maneiras de contornar isso (chamar outro construtor com o resultado de um método this(fn()) – o método é chamado antes do outro construtor e, portanto, também o super construtor) . Portanto, o poder de ponderar a proporção de fazer a mudança é desfavorável.

Editar Março de 2018: Na mensagem Registros: construção e validação O Oracle está sugerindo que essa restrição seja removida (mas, ao contrário do C #, this será definitivamente não atribuído (DU) antes do encadeamento do construtor).

Historicamente, this () ou super () deve ser o primeiro em um construtor. Essa restrição nunca foi popular e percebida como arbitrária. Houve várias razões sutis, incluindo a verificação do invokepecial, que contribuíram para essa restrição. Ao longo dos anos, abordamos isso no nível da VM, a ponto de se tornar prático considerar a possibilidade de elevar essa restrição, não apenas para registros, mas para todos os construtores.

Tenho bastante certeza (aqueles que estão familiarizados com o chime in Java Specification) de que é para evitar que você (a) tenha permissão para usar um object parcialmente construído, e (b), forçando o construtor da class pai a construir em um “novo “object.

Alguns exemplos de uma coisa “ruim” seriam:

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

Você perguntou por que, e as outras respostas, realmente não dizem por que é correto chamar o construtor do seu super, mas apenas se for a primeira linha. A razão é que você não está realmente chamando o construtor. Em C ++, a syntax equivalente é

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

Quando você vê a cláusula inicializadora por conta própria assim, antes da chave aberta, você sabe que é especial. Ele é executado antes que qualquer um dos restos do construtor seja executado e, de fato, antes que qualquer uma das variables ​​do membro seja inicializada. Não é tão diferente para o Java. Há uma maneira de executar algum código (outros construtores) antes que o construtor seja realmente iniciado, antes que qualquer membro da subclass seja inicializado. E dessa forma é colocar a “chamada” (por exemplo, super ) na primeira linha. (De certa forma, esse super ou this é meio antes da primeira chave aberta, mesmo que você o digite depois, porque ele será executado antes de você chegar ao ponto em que tudo está totalmente construído.) Qualquer outro código após a chave aberta (como int c = a + b; ) faz o compilador dizer “oh, ok, nenhum outro construtor, podemos inicializar tudo então.” Por isso, ele é executado e inicializa sua superclass e seus membros e outros itens e, em seguida, começa a executar o código após a chave aberta.

Se, algumas linhas depois, encontrar algum código dizendo “oh sim quando você está construindo este object, aqui estão os parâmetros que eu quero que você passe para o construtor da class base”, é tarde demais e não faz algum sentido. Então você recebe um erro de compilador.

Simplesmente porque esta é a filosofia de inheritance. E de acordo com a especificação da linguagem Java, é assim que o corpo do construtor é definido:

ConstructorBody: {ExplicitConstructorInvocation opt BlockStatements opt }

A primeira instrução de um corpo de construtor pode ser:
-uma invocação explícita de outro construtor da mesma class (usando a palavra-chave “this”) OU
-da superclass direta (usando a palavra-chave “super”)

Se um corpo de construtor não começa com uma invocação de construtor explícita e o construtor sendo declarado não faz parte da class primordial Object, então o corpo do construtor começa implicitamente com uma invocação de construtor de superclass “super ();”, uma invocação do construtor de sua superclass direta que não aceita argumentos. E assim por diante … haverá toda uma cadeia de construtores chamados até o construtor de Object; “Todas as Classes na plataforma Java são Descendentes de Objeto”. Essa coisa é chamada de ” encadeamento de construtor “.

Agora porque é isso?
E a razão pela qual o Java definiu o ConstructorBody dessa maneira é que eles precisavam manter a hierarquia do object. Lembre-se da definição da inheritance; Está estendendo uma class. Com isso dito, você não pode estender algo que não existe. A base (a superclass) precisa ser criada primeiro, então você pode derivá-la (a subclass). É por isso que eles os chamavam de classs pai e filho; você não pode ter um filho sem pai.

Em um nível técnico, uma subclass herda todos os membros (campos, methods, classs aninhadas) de seu pai. E como os Construtores NÃO são membros (Eles não pertencem a objects. Eles são responsáveis ​​por criar objects), portanto, eles NÃO são herdados por subclasss, mas podem ser invocados. E como no momento da criação do object, somente UM construtor é executado . Então, como garantimos a criação da superclass quando você cria o object da subclass? Assim, o conceito de “encadeamento de construtor”; então temos a capacidade de invocar outros construtores (ou seja, super) de dentro do construtor atual. E o Java exigiu que essa invocação fosse a PRIMEIRA linha no construtor da subclass para manter a hierarquia e garanti-la. Eles assumem que, se você não criar explicitamente o object pai PRIMEIRO (como se você tivesse esquecido), eles o farão implicitamente para você.

Essa verificação é feita durante a compilation. Mas não tenho certeza do que aconteceria no tempo de execução, que tipo de erro de tempo de execução obteríamos, se o Java não lança um erro de compilation quando explicitamente tentamos executar um construtor de base a partir do construtor de uma subclass no meio de seu corpo e não desde a primeira linha …

Então, não está impedindo você de executar a lógica antes da chamada para o super. É apenas impedi-lo de executar a lógica que você não pode se encheckboxr em uma única expressão.

Na verdade, você pode executar lógica com várias expessões, você apenas tem que quebrar seu código em uma function estática e chamá-lo na super instrução.

Usando seu exemplo:

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

Eu concordo totalmente, as restrições são muito fortes. Usar um método auxiliar estático (como Tom Hawtin – tackline sugeriu) ou empurrar todos os “cálculos pre-super ()” para uma única expressão no parâmetro nem sempre é possível, por exemplo:

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

Usar uma exceção “object ainda não construído”, como sugeriu Carson Myers, ajudaria, mas verificar isso durante a construção de cada object retardaria a execução. Eu preferiria um compilador Java que fizesse uma diferenciação melhor (em vez de proibir inconsequentemente uma instrução if, mas permitir o operador-dentro do parâmetro), mesmo que isso complique a especificação da linguagem.

Meu palpite é que eles fizeram isso para tornar a vida mais fácil para as pessoas que escrevem ferramentas que processam código Java e, em menor grau, também para pessoas que estão lendo código Java.

Se você permitir que a chamada super() ou this() se mova, haverá mais variações a serem verificadas. Por exemplo, se você mover a chamada super() ou this() para um if() condicional, talvez seja necessário ser inteligente o suficiente para inserir um super() implícito no else . Pode ser necessário saber como reportar um erro se você chamar super() duas vezes ou usar super() e this() juntos. Pode ser necessário não permitir chamadas de método no receptor até que super() ou this() seja chamado e descobrir quando isso se torna complicado.

Fazer todo mundo fazer esse trabalho extra provavelmente parecia um custo maior do que o benefício.

Você pode usar blocos inicializadores anônimos para inicializar campos no filho antes de chamar seu construtor. Este exemplo demonstrará:

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

Isto irá produzir:

Em pai
No inicializador
Na criança

Eu encontrei um woraround.

Isso não compilará:

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

Isso funciona :

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

Faz sentido que os construtores concluam sua execução na ordem de derivação. Como uma superclass não possui conhecimento de nenhuma subclass, qualquer boot que ela precise executar é separada e possivelmente pré-requisito para qualquer boot executada pela subclass. Portanto, deve concluir sua execução primeiro.

Uma demonstração simples:

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

A saída deste programa é:

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

Eu sei que estou um pouco atrasado para a festa, mas eu usei esse truque algumas vezes (e eu sei que é um pouco incomum):

Eu crio uma interface genérica InfoRunnable com um método:

 public T run(Object... args); 

E se eu precisar fazer algo antes de passá-lo para o construtor, basta fazer isso:

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

Na verdade, super() é a primeira instrução de um construtor, porque para garantir que sua superclass seja totalmente formada antes da construção da subclass. Mesmo se você não tiver o super() em sua primeira instrução, o compilador irá adicioná-lo para você!

Tldr:

As outras respostas abordaram o “porquê” da questão. Eu vou fornecer um truque em torno desta limitação:

A idéia básica é seqüestrar a superexposição com suas instruções incorporadas. Isso pode ser feito disfarçando suas declarações como expressões .

Tsdr:

Considere que queremos fazer Statement1() para Statement9() antes de chamar 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 } } 

O compilador irá, é claro, rejeitar nosso código. Então, ao invés disso, podemos fazer isso:

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

A única limitação é que a class pai deve ter um construtor que aceite pelo menos um argumento para que possamos nos esgueirar em nossa declaração como uma expressão.

Aqui está um exemplo mais elaborado:

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

Retrabalhado em:

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

Na verdade, os compiladores poderiam ter automatizado esse processo para nós. Eles apenas escolheram não fazer isso.

Antes de poder construir um object filho, seu object pai precisa ser criado. Como você sabe quando escreve aula assim:

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

vira para o próximo (estender e super são apenas escondidos):

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

Primeiro criamos um Object e depois estendemos esse object para MyClass . Nós não podemos criar MyClass antes do Object . A regra simples é que o construtor do pai deve ser chamado antes do construtor filho. Mas sabemos que as classs podem ter mais que um construtor. Java nos permite escolher um construtor que será chamado (ou será super() ou super(yourArgs...) ). Então, quando você escreve super(yourArgs...) você redefine o construtor que será chamado para criar um object pai. Você não pode executar outros methods antes de super() porque o object ainda não existe (mas depois de super() um object será criado e você poderá fazer o que quiser).

Então, por que então não podemos executar this() após qualquer método? Como você sabe, this() é o construtor da class atual. Também podemos ter diferentes números de construtores em nossa class e chamá-los assim this() ou this(yourArgs...) . Como eu disse, todo construtor escondeu o método super() . Quando escrevemos nosso super(yourArgs...) personalizado super(yourArgs...) nós removemos super() com super(yourArgs...) . Também quando definimos this() ou this(yourArgs...) nós também removemos nosso super() no construtor atual porque se super() estivesse com this() no mesmo método, ele criaria mais de um object pai. É por isso que as mesmas regras impostas para this() método this() . Ele apenas retransmite a criação do object pai para outro construtor filho e esse construtor chama o construtor super() para a criação pai. 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? Dois? 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; 

no fim. 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() .