what programming parameter new method example java constructor

java - programming - ¿Por qué esto() y super() tienen que ser la primera declaración en un constructor?



this in java programming (19)

Java requiere que si llamas a this () o super () en un constructor, debe ser la primera declaración. ¿Por qué?

Por ejemplo:

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

El compilador de Sun dice "call to super debe ser la primera declaración en el constructor". El compilador de Eclipse dice "La llamada al constructor debe ser la primera declaración en un constructor".

Sin embargo, puede solucionar esto reorganizando un poco el código:

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

Aquí hay otro ejemplo:

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

Por lo tanto, no le impide ejecutar la lógica antes de la llamada a super. Solo le impide ejecutar la lógica de que no puede encajar en una sola expresión.

Hay reglas similares para llamar a this() . El compilador dice "la llamada a esto debe ser la primera declaración en el constructor".

¿Por qué el compilador tiene estas restricciones? ¿Puede dar un ejemplo de código donde, si el compilador no tuviera esta restricción, sucediera algo malo?


Por lo tanto, no le impide ejecutar la lógica antes de la llamada a super. Solo le impide ejecutar la lógica de que no puede encajar en una sola expresión.

En realidad, puede ejecutar la lógica con varias expresiones, solo tiene que envolver su código en una función estática y llamarlo en la súper declaración.

Usando tu ejemplo:

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


Tiene sentido que los constructores completen su ejecución en orden de derivación. Debido a que una superclase no tiene conocimiento de ninguna subclase, cualquier inicialización que deba realizar es independiente y posiblemente sea un requisito previo para cualquier inicialización realizada por la subclase. Por lo tanto, debe completar su ejecución primero.

Una simple demostración:

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

La salida de este programa es:

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


¿Puede dar un ejemplo de código donde, si el compilador no tuviera esta restricción, sucediera algo malo?

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

Una excepción durante la construcción casi siempre indica que el objeto que se está construyendo no pudo inicializarse correctamente, ahora está en mal estado, es inutilizable y debe ser recolectado como basura. Sin embargo, un constructor de una subclase tiene la capacidad de ignorar una excepción ocurrida en una de sus superclases y devolver un objeto parcialmente inicializado. En el ejemplo anterior, si el argumento dado a new Bad()es 0 o mayor que 100, entonces ni essential1tampoco essential2se ha inicializado correctamente.

Puedes decir que ignorar las excepciones es siempre una mala idea. OK, aquí hay otro ejemplo:

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

Divertido, ¿no es así? ¿Cuántos objetos estamos creando en este ejemplo? ¿Uno? ¿Dos? O tal vez nada ...

Permitir llamar super()o this()en medio de un constructor abriría una caja de Pandora de constructores atroces.

Por otro lado, entiendo la necesidad frecuente de incluir alguna parte estática antes de una llamada a super()o this(). Esto podría ser cualquier código no depender de thisreferencia (que, de hecho, ya existe en el comienzo de un constructor, pero no puede ser utilizado hasta ordenada super()o this()devoluciones) y necesarios para realizar dicha llamada. Además, como en cualquier método, existe la posibilidad de que algunas variables locales creadas antes de la llamada super()o this()sean necesarias después.

En tales casos, tienes las siguientes oportunidades:

  1. Utilice el patrón presentado en esta respuesta , que permite eludir la restricción.
  2. Espere a que el equipo de Java permita el código previo super()y el this()código anterior . Puede hacerse imponiendo una restricción sobre dónde super()o this()puede ocurrir en un constructor. En realidad, incluso el compilador de hoy puede distinguir los casos buenos y malos (o potencialmente malos) con el grado suficiente para permitir la adición segura de código estático al comienzo de un constructor. De hecho, asuma eso super()y this()devuelva la thisreferencia y, a su vez, su constructor tiene

return this;

al final. Así como el compilador rechaza el código.

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

con el error "la variable x podría no haberse inicializado", podría hacerlo en la thisvariable, haciendo sus comprobaciones como en cualquier otra variable local. La única diferencia thisno puede asignarse por ningún otro medio que no super()sea this()call (y, como de costumbre, si no hay tal llamada en un constructor, el super()compilador la inserta implícitamente al principio) y puede que no se asigne dos veces. En caso de cualquier duda (como en la primera get(), donde en xrealidad siempre se asigna), el compilador podría devolver un error. Eso sería mejor que simplemente devolver el error en cualquier constructor donde haya algo excepto un comentario antes super()o this().


Tldr:

Las otras respuestas han abordado el "por qué" de la pregunta. Voy a proporcionar un hack alrededor de esta limitación:

La idea básica es secuestrar la super declaración con sus declaraciones incrustadas. Esto se puede hacer disfrazando sus declaraciones como expressions .

Tsdr:

Considere que queremos hacer Statement1() a Statement9() antes de llamar a 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 } }

El compilador, por supuesto, rechazará nuestro código. Así que en cambio, podemos hacer esto:

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

La única limitación es que la clase padre debe tener un constructor que tome al menos un argumento para que podamos colarnos en nuestra declaración como una expresión.

Aquí hay un ejemplo más 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 } }

Rediseñado en:

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

De hecho, los compiladores podrían haber automatizado este proceso para nosotros. Acababan de elegir no hacerlo.


Antes de poder construir un objeto secundario, debe crearse su objeto principal. Como sabes cuando escribes clase así:

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

pasa a la siguiente (extender y super están ocultos):

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

Primero creamos un Object y luego extendemos este objeto a MyClass . No podemos crear MyClass antes del Object . La regla simple es que el constructor principal se debe llamar antes que el constructor secundario.Pero sabemos que las clases pueden tener más de un constructor. Java nos permite elegir un constructor que será llamado (ya sea super()o super(yourArgs...)). Por lo tanto, cuando escriba super(yourArgs...), redefina el constructor al que se llamará para crear un objeto principal. No puedes ejecutar otros métodos antes super()porque el objeto aún no existe (pero después de super()que se creará un objeto y podrás hacer lo que quieras).

Entonces, ¿por qué entonces no podemos ejecutar this()ningún método? Como ustedes saben this()es el constructor de la clase actual. También podemos tener un número diferente de constructores en nuestra clase y llamarlos como this()o this(yourArgs...). Como dije todos los constructores han ocultado el método super(). Cuando escribimos nuestra costumbre super(yourArgs...)eliminamos super()con super(yourArgs...). También cuando definimos this()o this(yourArgs...)también eliminamos nuestra super()en el constructor actual, porque si super()estaban con this()el mismo método, sería crear más de un objeto padre. Es por eso que las mismas reglas impuestas para el this()método. Simplemente retransmite la creación del objeto principal a otro constructor secundario y ese constructor llamasuper()Constructor para la creación de padres. Entonces, el código será así de hecho:

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

Como otros dicen, puedes ejecutar código como este:

this(a+b);

También puedes ejecutar código como este:

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

Pero no puedes ejecutar código como este porque tu método aún no existe:

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

También estás obligado a tener super()constructor en tu cadena de this()métodos. No puedes tener una creación de objeto como esta:

public MyClass{ public MyClass(int a) { this(a, 5); } public MyClass(int a, int b) { this(a); } }


El constructor clase padre debe llamarse antes que el constructor la subclase. Esto asegurará que si llama a cualquier método en la clase padre en su constructor, la clase padre ya se ha configurado correctamente.

Lo que está tratando de hacer, pasar argumentos al super constructor es perfectamente legal, solo necesita construir esos argumentos en línea mientras lo hace, o pasarlos a su constructor y luego pasarlos a super :

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

Si el compilador no hiciera cumplir esto, podrías hacer esto:

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

En los casos en que una clase parent tiene un constructor predeterminado, el compiler inserta automáticamente la llamada a super. Dado que cada clase en Java hereda de Object , el constructor de objetos debe llamarse de alguna manera y debe ejecutarse primero. La inserción automática de super () por el compilador lo permite. Si obliga a que aparezca súper, se obliga a que los cuerpos de los constructores se ejecuten en el orden correcto, que sería: Objeto -> Padre -> Hijo -> ChildOfChild -> SoOnSoForth


En realidad, super() es la primera declaración de un constructor porque para asegurarse de que su superclase esté formada completamente antes de que se construya la subclase. Incluso si no tiene super() en su primera declaración, ¡el compilador lo agregará por usted!


Encontré un mundo.

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

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


Eso es porque su constructor depende de otros constructores. Para que su constructor funcione correctamente, es necesario que otro constructor funcione correctamente, lo cual depende. Es por eso que es necesario verificar primero los constructores dependientes, que fueron llamados por this () o super () en su constructor. Si otros constructores que llamaron por this () o super () tienen un problema, ¿qué punto ejecuta otras declaraciones porque todas fallarán si falla el constructor llamado?


Estoy bastante seguro (aquellos que están familiarizados con el sonido de la Especificación Java en) de que es para evitar que (a) se le permita usar un objeto parcialmente construido, y (b), lo que obliga al constructor de la clase padre a construir en un "nuevo "objeto.

Algunos ejemplos de algo "malo" serían:

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


Estoy totalmente de acuerdo, las restricciones son demasiado fuertes. No siempre es posible utilizar un método auxiliar estático (como sugirió Tom Hawtin - tacklea) o combinar todos los "cálculos previos (super-super) () en una sola expresión en el parámetro, por ejemplo:

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 una excepción de "objeto aún no construido", como sugirió Carson Myers, ayudaría, pero al verificar esto durante cada construcción del objeto se ralentizaría la ejecución. Preferiría un compilador de Java que haga una mejor diferenciación (en lugar de prohibir de manera consecuente una sentencia if, pero permitiendo al operador? Dentro del parámetro), incluso si esto complica la especificación del idioma.


He encontrado una forma de evitar esto mediante el encadenamiento de constructores y métodos estáticos. Lo que quise hacer era algo como esto:

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

Básicamente, construya un objeto basado en parámetros de constructor, almacene el objeto en un miembro y también pase el resultado de un método en ese objeto al constructor de super. Hacer que el miembro sea final también fue razonablemente importante ya que la naturaleza de la clase es que es inmutable. Tenga en cuenta que a medida que ocurre, la construcción de Bar en realidad toma algunos objetos intermedios, por lo que no se puede reducir a una sola línea en mi caso de uso real.

Terminé haciéndolo funcionar algo como esto:

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, y cumple la tarea de ejecutar varias declaraciones antes de llamar al súper constructor.


Porque el JLS lo dice. ¿Se podría cambiar el JLS de una manera compatible para permitirlo? Sip. Sin embargo, complicaría la especificación de lenguaje, que ya es lo suficientemente complicada. No sería muy útil hacerlo y hay formas de evitarlo (llame a otro constructor con el resultado de un método this(fn()) : el método se llama antes que el otro constructor y, por lo tanto, también el súper constructor) . Por lo tanto, la relación entre potencia y peso de hacer el cambio es desfavorable.

Edición de marzo de 2018: en el registro de mensajes : construcción y validación Oracle está sugiriendo que se elimine esta restricción (pero a diferencia de C #, this será definitivamente sin asignar (DU) antes del encadenamiento de constructores).

Históricamente, este () o super () debe ser el primero en un constructor. Esta restricción nunca fue popular, y percibida como arbitraria. Hubo una serie de razones sutiles, incluida la verificación de invokespecial, que contribuyeron a esta restricción. A lo largo de los años, hemos abordado estos temas a nivel de VM, hasta el punto en que resulta práctico considerar eliminar esta restricción, no solo para los registros, sino para todos los constructores.


Preguntó por qué, y las otras respuestas, imo, no dicen realmente por qué está bien llamar al constructor de su súper, pero solo si es la primera línea. La razón es que realmente no estás llamando al constructor. En C ++, la sintaxis equivalente es

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

Cuando ves la cláusula de inicialización de esa manera, antes de la llave abierta, sabes que es especial. Se ejecuta antes de que se ejecute el resto del constructor y, de hecho, antes de que se inicialice cualquiera de las variables miembro. No es tan diferente para Java. Hay una manera de conseguir que se ejecute algún código (otros constructores) antes de que el constructor se inicie realmente, antes de que se inicialice cualquier miembro de la subclase. Y de esa manera es poner la "llamada" (por ejemplo, super ) en la primera línea. (En cierto modo, ese super o this es un poco antes del primer corchete abierto, aunque lo escriba después, porque se ejecutará antes de llegar al punto en que todo esté completamente construido). Cualquier otro código después del tirante abierto (como int c = a + b; ) hace que el compilador diga "oh, ok, no hay otros constructores, entonces podemos inicializar todo". Por lo tanto, se ejecuta e inicializa su súper clase y sus miembros y otras cosas, y luego comienza a ejecutar el código después de la llave abierta.

Si, unas cuantas líneas después, se encuentra con un código que dice "oh, sí, cuando esté construyendo este objeto, aquí están los parámetros que quiero que transmita al constructor para la clase base", es demasiado tarde y no lo hace. ningún sentido. Entonces obtienes un error de compilación.


Puede usar bloques de inicialización anónimos para inicializar los campos en el hijo antes de llamar a su constructor. Este ejemplo demostrará:

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

Esto dará como resultado:

En padre
En inicializador
En niño


Sé que llego un poco tarde a la fiesta, pero he usado este truco un par de veces (y sé que es un poco inusual):

Creo una interfaz genérica de InfoRunnable<T> con un método:

public T run(Object... args);

Y si necesito hacer algo antes de pasárselo al constructor, simplemente hago esto:

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


Simplemente porque esta es la filosofía de la herencia. Y de acuerdo con la especificación del lenguaje Java, así es como se define el cuerpo del constructor:

ConstructorBody: {ExplicitConstructorInvocation opt BlockStatements opt }

La primera declaración de un cuerpo constructor puede ser:
-una invocación explícita de otro constructor de la misma clase (usando la palabra clave "this") O
-de la superclase directa (usando la palabra clave "super")

Si el cuerpo de un constructor no comienza con una invocación explícita del constructor y el constructor que se está declarando no es parte de la clase primordial Object, entonces el cuerpo del constructor comienza implícitamente con una invocación de superclase "super ();", una invocación del constructor Su superclase directa que no lleva argumentos. Y así sucesivamente ... habrá una cadena completa de constructores llamados de regreso al constructor de Object; "Todas las clases en la plataforma Java son descendientes de objetos". Esta cosa se llama " Constructor de encadenamiento ".

Ahora por que es esto
Y la razón por la que Java definió el ConstructorBody de esta manera, es que necesitaban mantener la jerarquía del objeto. Recuerda la definición de la herencia; Está extendiendo una clase. Dicho esto, no puedes extender algo que no existe. La base (la superclase) debe crearse primero, luego puede derivarla (la subclase). Por eso los llamaron clases de padres e hijos; No puedes tener un hijo sin un padre.

En un nivel técnico, una subclase hereda todos los miembros (campos, métodos, clases anidadas) de su padre. Y dado que los Constructores NO son miembros (no pertenecen a objetos, son responsables de crear objetos), por lo que NO son heredados por subclases, pero pueden invocarse. Y ya que en el momento de la creación del objeto solo se ejecuta UN constructor . Entonces, ¿cómo garantizamos la creación de la superclase cuando crea el objeto de subclase? Así, el concepto de "encadenamiento de constructores"; así que tenemos la capacidad de invocar a otros constructores (es decir, super) desde el constructor actual. Y Java requirió que esta invocación fuera la PRIMERA línea en el constructor de la subclase para mantener la jerarquía y garantizarla. Suponen que si no crea explícitamente el objeto primario PRIMERO (como si lo olvidara), lo harán implícitamente por usted.

Esta comprobación se realiza durante la compilación. Pero no estoy seguro de qué sucedería en el tiempo de ejecución, qué tipo de error de tiempo de ejecución obtendríamos, si Java no produce un error de compilación cuando intentamos explícitamente ejecutar un constructor base desde el constructor de una subclase en medio de su Cuerpo y no desde la primera línea ...


Supongo que hicieron esto para facilitar la vida a las personas que escriben herramientas que procesan el código Java y, en menor medida, también a las personas que están leyendo el código Java.

Si permite que la llamada super() o this() mueva, hay más variaciones para verificar. Por ejemplo, si mueve la llamada super() o this() a un condicional if() podría tener que ser lo suficientemente inteligente como para insertar una super() implícita en el else . Es posible que deba saber cómo reportar un error si llama a super() dos veces, o si usa super() y this() juntos. Es posible que deba rechazar las llamadas de método en el receptor hasta que se llame a super() o this() y descifrar cuándo se complica.

Hacer que todos hicieran este trabajo extra probablemente parecía un mayor costo que un beneficio.


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

Vea el ejemplo si estamos llamando al constructor, C(int x)entonces el valor de z depende de y si no llamamos C()en la primera línea, entonces será el problema para z. z no podría obtener el valor correcto.