¿Por qué deben ser this () y super () la primera declaración en el constructor?

Why Must This Super Be First Statement Constructor

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

P.ej:



|_+_|

El compilador de Sun dice que 'la llamada a super debe ser la primera declaración del constructor'. El compilador de Eclipse dice que 'la llamada al constructor debe ser la primera declaración en el constructor'.



Sin embargo, puede resolver este problema reorganizando algunos códigos:



|_+_|

Este es otro ejemplo:

|_+_|

Así que esto No te detendré Antes de llamar super Lógica de ejecución . Esto solo le impide ejecutar una lógica que no puede estar contenida en una sola expresión.

transfer 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 } } Hay reglas similares. 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, si el compilador no tiene esta limitación, entonces sucederá algo malo?


#1er piso

Padre public class MySubClass extends MyClass { public MySubClass(int a, int b) { super(a + b) // OK } } Subclase 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 } } Antes de llamar. Esto asegurará que si llama a cualquier método de la clase principal en el constructor, la clase principal se haya configurado correctamente.

Lo que quiere hacer es que es perfectamente legal pasar argumentos al superconstructor, solo necesita construir estos argumentos en línea mientras realiza la operación, o pasarlos al constructor y luego pasarlos a this()

|_+_|

Si el compilador no aplica esta operación, puede hacer lo siguiente:

|_+_|

en caso de que constructor La clase tenga un constructor predeterminado, entonces constructor La llamada a super se insertará automáticamente. Dado que cada clase en Java hereda de super Por lo tanto, el constructor del objeto debe llamarse de alguna manera y debe ejecutarse primero. El compilador inserta automáticamente super () para lograr esto. Forzar a super para que aparezca al frente y obligar al cuerpo del constructor a ejecutarse en el orden correcto, a saber: Objeto -> Padre -> Niño -> Niño de niño-> SoOnSoForth


#2 ° piso

Estoy bastante seguro (una persona familiarizada con la especificación de Java) de que esto es para evitar que (a) se le permita usar objetos parcialmente construidos y (b) forzar al constructor del padre a 'construir' objetos 'recientemente'.

Algunos ejemplos de cosas 'malas' son:

|_+_|

# 3er piso

Puede usar bloques inicializadores anónimos para inicializar los campos en el niño antes de llamar al constructor del niño. Este ejemplo demostrará:

|_+_|

Esto dará como resultado:

Entre padres
En el inicializador
Entre los niños


#4to piso

Porque JLS lo dijo. ¿Es posible cambiar JLS de una manera compatible para permitirlo? Correcto.

Sin embargo, esto complica la especificación del lenguaje, que no es lo suficientemente complicada. Esto no es algo muy útil y hay muchas soluciones (usando métodos estáticos o los resultados de expresiones lambda public MySubClassB extends MyClass { public MySubClassB(Object[] myArray) { super(myArray) } } Llamar a otro constructor; este método se llama antes que otro constructor, por lo que también es un superconstructor). Por tanto, la relación potencia-peso modificada es desventajosa.

Tenga en cuenta que esta regla por sí sola no impide el uso de campos hasta que la superclase haya completado la construcción.

Considere estos ejemplos ilegales.

|_+_|

Este ejemplo es legal, pero 'incorrecto'.

|_+_|

En el ejemplo anterior, si public MySubClassB extends MyClass { public MySubClassB(Object[] myArray) { someMethodOnSuper() //ERROR super not yet constructed super(myArray) } } necesita parent Los parámetros del constructor, debe usar compiler Ocultarlos. (

Por cierto, dado que Java 1.4 contiene campos de síntesis externos Object, se asigna antes de que se llame al superconstructor de la clase interna. Esto provocó un especial en el código compilado para la versión anterior del evento de destino 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 } } .

Tenga en cuenta también que en presencia de publicaciones inseguras, a menos que se tomen precauciones, la construcción se puede revisar a través de otros hilos.

Edición de marzo de 2018: En las noticias Registro: Construcción y Verificación Oracle sugiere que se eliminó esta restricción (pero a diferencia de C #, 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') } } estará antes de la cadena del constructor Ciertamente no asignado (DE)).

Históricamente, este () o super () debe estar primero en el constructor. Esta restricción nunca ha sido popular y se considera arbitraria. Hay muchas razones sutiles para esta limitación, incluida la verificación de invocarespecial. A lo largo de los años, hemos resuelto estos problemas a nivel de máquina virtual, por lo que considerar levantar esta restricción no solo es factible para los registros, sino también para todos los constructores.


#5to piso

Solo porque esta es la filosofía de la herencia. Y de acuerdo con la especificación del lenguaje Java, esta es la forma de definir el cuerpo del constructor:

ConstructorBody:{ExplicitConstructorInvocationoptarBlockStatementsoptar}

La primera declaración en el cuerpo del constructor puede ser

  • Llame explícitamente a otro constructor de la misma clase (usando la palabra clave 'this') o
  • Invocación explícita de superclases directas (utilizando la palabra clave 'super')

Si el cuerpo del constructor no comienza con una llamada de constructor explícita, y el constructor declarado no es parte de la clase original Object, el cuerpo del constructor comenzará implícitamente con una llamada de constructor de superclase 'super ()' La llamada de constructor es su superclase directa con sin parámetros. Y así sucesivamente ... Habrá una cadena completa de constructores, hasta el constructor de Object 'Todas las clases en la plataforma Java son descendientes de objetos'. Esta cosa se llama ' Enlace de constructor ”。

¿Porqué ahora?
La razón por la que Java define ConstructorBody de esta manera es que necesitan mantener la jerarquía de objetos. Recuerde que la definición de herencia amplía el plan de estudios. Dicho esto, no se puede extender algo que no existe. Primero necesita crear una base (superclase) y luego puede derivarla (subclase). Por eso los llaman 'padres e hijos'. No se pueden tener hijos sin padres.

Técnicamente, la subclase hereda todos los miembros (campos, métodos, clases anidadas) de su clase principal. Y como los constructores no son miembros (no son objetos. Son responsables de crear objetos), no serán heredados por subclases, pero pueden ser llamados. Y porqué Solo se ejecuta un constructor al crear un objeto . Entonces, cuando crea un objeto de subclase, ¿cómo garantiza la creación de una superclase? Por lo tanto, el concepto de 'vínculo de construcción', por lo tanto, podemos llamar a otros constructores (es decir, super) del constructor actual. Java requiere que esta llamada sea la PRIMERA línea en el constructor de subclase para mantener la jerarquía y asegurar la jerarquía. Asumen que si no crea explícitamente el objeto padre PRIMERO (como si lo hubiera olvidado), lo crearán implícitamente para usted.

Esta comprobación se realiza durante la compilación. Pero no estoy seguro de qué sucederá en tiempo de ejecución, qué tipo de error de tiempo de ejecución obtendremos, cuando intentemos ejecutar explícitamente el constructor básico desde el constructor básico dentro del constructor de la subclase, si Java no arroja un error de compilación, el cuerpo, no desde la primera línea ...


#6 ° piso

Supongo que esto se hace para que sea más fácil para las personas que escriben herramientas que procesan código Java y para las personas que leen código Java.

Si está permitido this(fn()) o super(this.x = 5) super(this.fn()) super(fn()) super(x) super(this instanceof SubClass) // this.getClass() would be /really/ useful sometimes. Al llamar al móvil, hay más cambios para verificar. Por ejemplo, si class MyBase { MyBase() { fn() } abstract void fn() } class MyDerived extends MyBase { void fn() { // ??? } } o MyDerived.fn Llamar para pasar a la condición MyDerived Puede que tenga que ser lo suficientemente inteligente para hacer implícito ThreadLocal insertar this . Si llama dos veces NullPointerException O usa juntos this con super() , Es posible que deba saber cómo informar un error. Puede ser necesario prohibir las llamadas a métodos en el receptor hasta la llamada this() o super() Hasta ahora, y averiguar cuándo se vuelve complicado.

Conseguir que todos realicen estos trabajos adicionales parece ser más caro que pagar.


#7 ° piso

Por lo tanto, esto no le impide ejecutar la lógica antes de llamar a super. Esto solo le impide ejecutar una lógica que no puede estar contenida en una sola expresión.

De hecho, puede usar varios métodos para ejecutar la lógica, simplemente envuelva el código en una función estática y luego llámelo en la instrucción super.

Usa tu ejemplo:

|_+_|

# 8 piso

Tldr:

Las otras respuestas resuelven la 'causa' del problema. Proporcionaré algo para esta limitación. habilidad

La idea básica es usar tu oración incrustada secuestrar this() Declaración. Esto se puede hacer disfrazando la declaración como expresión Para acabar.

Tsdr:

Considere que estamos llamando if() Antes de super() realizado else super()

|_+_|

El compilador, por supuesto, rechazará nuestro código. Por lo tanto, podemos hacer esto:

|_+_|

La única limitación es, La clase padre debe tener un constructor con al menos un parámetro, Para que podamos colar expresiones en expresiones.

Este es un ejemplo más detallado:

|_+_|

Reelaborado como:

|_+_|

De hecho, el compilador puede automatizar este proceso por nosotros. Simplemente eligieron no hacerlo.


# 9 piso

Encontré una solución.

Esto no compilará:

|_+_|

Esto funciona:

|_+_|

#piso 10

Sé que llego un poco tarde a la fiesta, pero he usado esta técnica varias veces (y sé que es un poco inusual):

Utilizo un método para crear una interfaz común super()

|_+_|

Si necesita hacer algo antes de pasarlo al constructor, haga lo siguiente:

|_+_|

# 11 piso

Antes de construir un objeto hijo, primero debe crear un objeto padre. Como sabes, cuando escribes una clase de este tipo:

|_+_|

Ir al siguiente (extendido y super oculto):

|_+_|

Primero, creamos un this() Y luego expanda el objeto a super() . No podemos this() Creado antes de 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 } } . Una regla simple es que se debe llamar al constructor padre antes que al constructor hijo. Pero sabemos que una clase puede tener más de un constructor. Java nos permite elegir un constructor al que llamar (será super o super()). Por lo tanto, cuando escriba Statement9() redefinirá el constructor, al que se llamará para crear el objeto principal. No puedes Statement1() antes de realizar otros métodos porque el objeto aún no existe (pero en super(), se creará un objeto y podrás realizar las operaciones que necesites).

Entonces, ¿por qué no se puede ejecutar después de cualquier método 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 } } ¿Qué? Como sabes, // 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 } } Es el constructor de la clase actual. De manera similar, podemos tener un número diferente de constructores en nuestra clase, y como 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 } } o // 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 } } Llámalos así. Como dije, cada constructor tiene un método oculto 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) } } . Cuando escribimos personalizado 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) } } Cuando usamos InfoRunnable eliminado public T run(Object... args) super(new InfoRunnable() { public ThingToPass run(Object... args) { /* do your things here */ } }.run(/* args here */)) . De manera similar, cuando definamos public MyClass { public MyClass(String someArg) { System.out.println(someArg) } } o public MyClass extends Object{ public MyClass(String someArg) { super() System.out.println(someArg) } } también borraremos nuestro en el constructor actual Object Porque si MyClass versus Object Usados ​​juntos en el mismo método, creará más de un objeto principal. Es por eso que right MyClass La razón por la que el método impone las mismas reglas. Simplemente pasa la creación del objeto padre a otro constructor hijo, que se llama super() El constructor crea el padre. Por lo tanto, el código se verá así:

|_+_|

Como han dicho otros, puede ejecutar el siguiente código:

|_+_|

También puede ejecutar el siguiente código:

|_+_|

Pero no puede ejecutar código como este, porque su método aún no existe:

|_+_|

Además, debe super(yourArgs...) En la cadena de métodos super(yourArgs...) Constructor. No puedes crear objetos como este:

|_+_|

# 12 piso

|_+_|

Ver ejemplo, si llamamos al constructor super() entonces el valor de z depende de y, si no llamamos en la primera línea super() Entonces habrá problemas con z. z no obtendrá el valor correcto.


# 13 piso

Tiene sentido que el constructor complete su ejecución en el orden de derivación. Debido a que la superclase no comprende ninguna subclase, cualquier inicialización que deba realizar está separada de cualquier inicialización realizada por la subclase y puede ser un requisito previo. Por lo tanto, primero debe completar su ejecución.

Una simple demostración:

|_+_|

El resultado de este programa es:

|_+_|

# 14F

En realidad, this() Es la primera declaración del constructor, porque es necesario asegurarse de que su superclase esté completamente formada antes de que se construya la subclase. Incluso si su primera declaración no es this() ¡Y el compilador también lo agregará por usted!


# 15F

¿Puede dar un ejemplo de código, si el compilador no tiene esta limitación, entonces sucederá algo malo?

|_+_|

Una excepción durante la construcción casi siempre significa que el objeto que se está construyendo no se puede inicializar correctamente, ahora está en un estado de error, no se puede usar y debe recolectarse basura. Sin embargo, el constructor de la subclase puede ignorar la excepción que ocurrió en una de sus clases principales y devolver el objeto parcialmente inicializado. En el ejemplo anterior, si se proporciona a this() Parámetro de this(yourArgs...) 0 o mayor que 100, entonces super() con super(yourArgs...) Ninguno se inicializará correctamente.

Podría decirse que ignorar las excepciones siempre es una mala idea. Ok, aquí hay otro ejemplo:

|_+_|

Gracioso, ¿no es así? En este ejemplo, ¿cuántos objetos queremos crear? ¿Uno? ¿dos? Quizás nada ...

Permitir llamadas en el medio del constructor super(yourArgs...) o super() El desagradable constructor que abrirá la caja de Pandora.


Por otro lado, sé que super(yourArgs...) o this() Solía ​​ser necesario incluir algunas partes estáticas. Esto puede ser cualquier cosa que no dependa de this(yourArgs...) El código entre comillas (en realidad, super() La referencia ya existe al principio del constructor, pero en super() o this() No se puede usado en orden antes de regresar) y tales llamadas son requeridas. Además, como en cualquier método, es posible llamar a this() o super() Algunas variables locales deben crearse antes.

En este caso, tiene las siguientes oportunidades:

  1. usar En esta respuesta El modo de visualización puede evitar esta limitación.
  2. Espere a que el equipo de Java permita pre 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) } } y pre this(a+b) Code. Esto se puede hacer mediante public MyClass(int a, SomeObject someObject) { this(someObject.add(a+5)) } o 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 } } Esto se hace imponiendo restricciones a las ubicaciones que pueden aparecer en el constructor. De hecho, incluso los compiladores actuales pueden distinguir entre situaciones buenas y malas (o potencialmente malas) en un grado suficiente para permitir que se agregue de forma segura código estático al comienzo del constructor. De hecho, suponga que this() con super() return public MyClass{ public MyClass(int a) { this(a, 5) } public MyClass(int a, int b) { this(a) } } Reference, entonces, el constructor
|_+_|

al final. Y el compilador rechaza el código

|_+_|

Error 'Es posible que la variable x no se haya inicializado', puede 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) } } Ejecución de la variable C(int x) , Al igual que cualquier otra variable local, compruébalo. La única diferencia es C() No se puede especificar con ningún otro método 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() } } o Inside A's constructor Inside B's constructor Inside C's constructor Llamar (como de costumbre, si no hay tal llamada en una construcción, super() Se inserta implícitamente por el compilador al principio) y no puede asignarse dos veces. Si tiene alguna pregunta (por ejemplo, en el primer super(), En realidad 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) } } Siempre asignado), el compilador puede devolver un error. Esto es mejor que cualquier new Bad() o new Bad() Es mejor simplemente devolver un error en el constructor excepto por el comentario anterior.


# 16 piso

Eso es porque su constructor depende de otros constructores. Para que su constructor funcione correctamente, es esencial para otras funciones normales que dependen de otros constructores. Esta es la razón por la que primero se debe verificar el constructor de dependencias, que es llamado por this () o super () en el constructor. Si hay problemas con otros constructores llamados a través de this () o super (), entonces cuál es la ejecución de otras declaraciones, porque si el constructor llamado falla, todas las declaraciones fallarán.


# 17 piso

Estoy totalmente de acuerdo en que las restricciones son demasiado estrictas. No siempre es posible utilizar métodos auxiliares estáticos (como sugiere Tom Hawtin-líneas viscosas) o empujar todos los 'cálculos pre-super ()' en una sola expresión en los parámetros, por ejemplo:

|_+_|

Como sugirió Carson Myers, el uso de la excepción 'objeto no construido' ayudará, pero verificar esta excepción cada vez que construya un objeto ralentizará la ejecución. Espero que el compilador de Java pueda hacer una mejor distinción (en lugar de deshabilitar las declaraciones if, pero permitiendo el uso de operadores? En los parámetros), incluso si esto complica la especificación del lenguaje.


# 18 piso

Usted pregunta por qué, otras respuestas imo realmente no dijo por qué puede llamar a su superconstructor, pero solo si es la primera línea. La razón es que realmente no transferir Constructor. En C ++, la sintaxis equivalente es

|_+_|

Cuando vea la cláusula inicializadora como esta, sabrá que es especial antes de las llaves. Se ejecuta antes de que se ejecute el resto del constructor, en realidad antes de que se inicialicen las variables miembro. No es diferente para Java. Existe una forma de que se ejecute algún código (otros constructores) antes de que el constructor se inicie realmente y antes de que se inicialice cualquier miembro de la subclase. En ese caso, ponga 'llamada' (por ejemplo essential1) en la primera línea. (en cierto sentido, essential2 o class Bad extends Good { Bad(int n) { for (int i = 0 i Un poco antes del primer corchete de apertura, incluso si lo escribe más tarde, porque se ejecutará antes de que haya construido todo por completo). (Por ejemplo super()) Haga que el compilador diga 'Oh, está bien, sin otros constructores, entonces podemos inicializar todo'. Entonces comienza a ejecutarse e inicializa sus superclases, miembros y similares, y luego comienza la ejecución después de las llaves Code.

Si después de unas pocas líneas, encuentra algún código que dice 'Oh, sí, cuando construyes este objeto, este es el parámetro que quiero que pases al constructor de la clase base', es demasiado tarde y no hay No tiene sentido. Por lo tanto, recibirá un error del compilador.


# 19F

Al vincular el constructor y el método estático, encontré una solución a este problema. Lo que quiero hacer se ve así:

|_+_|

Por lo tanto, básicamente construya un objeto basado en los parámetros del constructor, almacene el objeto en un miembro y pase los resultados del método en el objeto al superconstructor. También es importante que los miembros sean los miembros finales, porque la naturaleza de la clase es constante. Tenga en cuenta que, como sucede, la construcción de Bar en realidad requiere varios objetos intermedios, por lo que en mi caso de uso real, no se puede reducir a una sola forma.

Terminé haciéndolo funcionar así:

|_+_|

Código legal, que completa la tarea de ejecutar múltiples declaraciones antes de llamar al superconstructor.