¿Por qué esta () y super () tienen que ser la primera declaración en un constructor?
Frecuentes
Visto 254,537 equipos
637
Java requiere que si llama 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 que "la llamada a super debe ser la primera instrucción en el constructor". El compilador de Eclipse dice "La llamada al constructor debe ser la primera instrucció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
}
}
Así es sin impedirte ejecutar la lógica antes de la llamada a super. Simplemente le impide ejecutar la lógica que no puede encajar en una sola expresión.
Existen reglas similares para llamar this()
. El compilador dice "la llamada a esto debe ser la primera instrucción en el constructor".
¿Por qué el compilador tiene estas restricciones? ¿Puede dar un ejemplo de código en el que, si el compilador no tuviera esta restricción, sucedería algo malo?
21 Respuestas
191
La clase padre ' constructor
necesita ser llamado antes que la subclase ' constructor
. Esto asegurará que si llama a cualquier método en la clase principal en su constructor, la clase principal ya se ha configurado correctamente.
Lo que está tratando de hacer, pasar argumentos al superconstructor es perfectamente legal, solo necesita construir esos argumentos en línea mientras lo está haciendo, o pasarlos a su constructor y luego pasarlos a super
:
public MySubClassB extends MyClass {
public MySubClassB(Object[] myArray) {
super(myArray);
}
}
Si el compilador no hizo cumplir esto, puede hacer esto:
public MySubClassB extends MyClass {
public MySubClassB(Object[] myArray) {
someMethodOnSuper(); //ERROR super not yet constructed
super(myArray);
}
}
En los casos en que un parent
la clase tiene un constructor predeterminado, la llamada a super se inserta automáticamente por el compiler
. 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 parte del compilador lo permite. Al hacer que super aparezca primero, obliga a que los cuerpos del constructor se ejecuten en el orden correcto, que sería: Objeto -> Padre -> Niño -> Niño de niño -> SoOnSoForth
Respondido 19 ago 15, 15:08
Creo que no estoy de acuerdo, por dos razones ... (1) Comprobar que super es la primera afirmación no es suficiente para prevenir ese problema. Por ejemplo, podría poner "super (someMethodInSuper ());" en su constructor. Esto intenta acceder a un método en la superclase antes de que se construya, aunque super es la primera declaración. (2) El compilador parece implementar una verificación diferente que, por sí sola, es suficiente para evitar este problema. El mensaje es "no se puede hacer referencia a xxx antes de que se haya llamado al constructor de supertipo". Por tanto, no es necesario comprobar que super es la primera afirmación. - joe daley
@Joe Tiene razón, colocar super () como la primera declaración no impide llamar a métodos en el padre antes de que se llame. Como mencionaste, es un cheque por separado. Sin embargo, ¿hace cumplir el orden en que se ejecutan los cuerpos de los constructores? ¿Acordado? Creo que esta es la razón por la que se llama a super () la primera declaración. - ángel
Teniendo en cuenta que el compilador sabe cuándo está accediendo a métodos / campos principales, no veo por qué no se le puede permitir algo como Constructor(int x) { this.field1 = x; super(); }
. Claro, no debería necesitarlo en un mundo ideal donde usted controla el código, pero no siempre es así. La razón por la que busqué esto en primer lugar fue porque estaba molesto por no poder usarlo para solucionar una falla en el código de terceros. - thor84no
De acuerdo con @JoeDaley, creo que el hecho de que C # no tenga esta restricción es suficiente para sugerir que este problema se puede resolver de maneras menos torpes. - tom lianza
Para su información, muy a menudo cuando parece que necesita hacer lógica antes de llamar super
, es mejor utilizar la composición en lugar de la herencia. - Alejandro Dubinsky
105
Encontré una forma de evitar esto encadenando constructores y métodos estáticos. Lo que quería hacer se parecía a 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 definitivo también fue razonablemente importante ya que la naturaleza de la clase es que es inmutable. Tenga en cuenta que, como sucede, la construcción de Bar en realidad requiere 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 de esta manera:
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 múltiples declaraciones antes de llamar al superconstructor.
respondido 15 mar '15, 10:03
Esta técnica se puede ampliar. Si super toma muchos parámetros o necesita establecer otros campos al mismo tiempo, cree una clase interna estática para contener todas las variables y úsela para pasar datos del método estático al constructor de un solo argumento. - Alejandro Dubinsky
Para su información, muy a menudo cuando parece que necesita hacer lógica antes de llamar super
, es mejor utilizar la composición en lugar de la herencia. - Alejandro Dubinsky
Me tomó un poco de tiempo entender su concepto. Entonces, básicamente, crea un método estático y lo coloca en el constructor. - muerte
@AleksandrDubinsky ¿Puede elaborar (proporcionar código de muestra) que muestre cómo usar una clase interna estática para establecer múltiples superparámetros al mismo tiempo? ¿Quizás esto se discute con más detalle en otra publicación a la que pueda vincular? - Gili
+1, esto soluciona el problema creado por la restricción de Java. Pero no responde a la pregunta de OP, ¿por qué Java los compiladores tienen estas restricciones? - 1Emáx
51
Porque la JLS lo dice. ¿Se podría cambiar el JLS de manera compatible para permitirlo? Sip.
Sin embargo, complicaría la especificación del idioma, que ya es lo suficientemente complicada. No sería algo muy útil de hacer y hay formas de evitarlo (llame a otro constructor con el resultado de un método estático o expresión lambda this(fn())
- el método se llama antes que el otro constructor y, por lo tanto, también el superconstructor). Entonces, la relación potencia / peso de hacer el cambio es desfavorable.
Tenga en cuenta que esta regla por sí sola no evita el uso de campos antes de que la superclase haya completado la construcción.
Considere estos ejemplos ilegales.
super(this.x = 5);
super(this.fn());
super(fn());
super(x);
super(this instanceof SubClass);
// this.getClass() would be /really/ useful sometimes.
Este ejemplo es legal, pero "incorrecto".
class MyBase {
MyBase() {
fn();
}
abstract void fn();
}
class MyDerived extends MyBase {
void fn() {
// ???
}
}
En el ejemplo anterior, si MyDerived.fn
argumentos requeridos de la MyDerived
constructor tendrían que ser reducidos con un ThreadLocal
. ; (
Por cierto, desde Java 1.4, el campo sintético que contiene el exterior this
se asigna antes de que se llame al superconstructor de clases internas. Esto provocó peculiar NullPointerException
eventos en código compilado para apuntar a versiones anteriores.
Tenga en cuenta también que, en presencia de una publicación insegura, la construcción puede verse reordenada por otros hilos, a menos que se tomen precauciones.
Edición de marzo de 2018: En mensaje Registros: construcción y validación Oracle sugiere que se elimine esta restricción (pero a diferencia de C #, this
se mostrarán definitivamente no asignado (DU) antes del encadenamiento del constructor).
Históricamente, este () o super () debe ser el primero en un constructor. Esta restricción nunca fue popular y se percibió como arbitraria. Hubo una serie de razones sutiles, incluida la verificación de invocarespecial, que contribuyeron a esta restricción. A lo largo de los años, los hemos abordado 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.
contestado el 29 de mayo de 19 a las 20:05
solo para aclarar: el fn () que usó en su ejemplo debería ser un método estático, ¿verdad? - Jason S
+1 por mencionar que esto es puramente una restricción de JLS. A nivel de código de bytes, puede hacer otras cosas antes de llamar a un constructor. - Antimonio
Espera, ¿cómo podría esto complicar la especificación del idioma? Y en el momento en que la especificación dice que la primera declaración puede ser un constructor, todas las demás declaraciones no pueden ser constructoras. Cuando elimine la restricción, la especificación será algo así como "solo tiene declaraciones dentro". ¿Cómo es esto más complicado? - uko
@Uko obtiene la respuesta cuando la compara con las partes de especificación JVM relevantes. Como dijo Antimony, esta restricción no existe en el nivel del código de bytes, pero, por supuesto, el requisito de invocar un superconstructor y no usar el objeto en construcción antes de que se haya llamado al superconstructor, aún existe. Entonces, la definición del código correcto y cómo verificar su exactitud llena páginas enteras. Ofrecer la misma libertad en JLS requiere una complejidad similar, ya que JLS no puede permitir cosas que son ilegales en el nivel de código de bytes. - Holger
@Holger En aras del equilibrio, observo que hay un código que se puede ejecutar en un constructor antes de la llamada a super()
/this()
. Este código son los argumentos si super()
/this()
tiene parámetros. Sin embargo, hay muchas más tonterías que podrías hacer con declaraciones completas. - Tom Hawtin - táctica
17
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: {ExplicitConstructorInvocationoptar declaraciones de bloqueoptar }
La primera declaración de un cuerpo de constructor puede ser
- una invocación explícita de otro constructor de la misma clase (utilizando la palabra clave "this"); o
- una invocación explícita de la superclase directa (mediante el uso de 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 declara no es parte de la clase primordial Object, entonces el cuerpo del constructor comienza implícitamente con una invocación de constructor de superclase "super ();", una invocación del constructor de su superclase directa que no acepta argumentos. Y así sucesivamente ... habrá toda una cadena de constructores llamados hasta el constructor de Object; "Todas las clases de la plataforma Java son descendientes de objetos". Esta cosa se llama "Encadenamiento de constructores".
Ahora, ¿por qué 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. Recuerde la definición de herencia; Está ampliando una clase. Dicho esto, no se puede extender algo que no existe. La base (la superclase) debe crearse primero, luego puede derivarla (la subclase). Por eso las llamaron clases para padres e hijos; no puedes tener un hijo sin un padre.
A 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 ser invocados. Y desde 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? De ahí el concepto de "encadenamiento de constructores"; por lo que tenemos la capacidad de invocar a otros constructores (es decir, super) desde dentro del constructor actual. Y Java requería que esta invocación fuera la PRIMERA línea en el constructor de subclase para mantener la jerarquía y garantizarla. Asumen que si no crea explícitamente el objeto padre PRIMERO (como si lo hubiera olvidado), lo harán implícitamente por usted.
Esta comprobación se realiza durante la compilación. Pero no estoy seguro de lo que sucedería en tiempo de ejecución, qué tipo de error de tiempo de ejecución obtendríamos, SI Java no arroja un error de compilación cuando intentamos explícitamente ejecutar un constructor base desde dentro del constructor de una subclase en medio de su cuerpo y no desde la primera línea ...
Respondido 01 Abr '19, 12:04
Sé que los constructores no se procesan como llamadas a funciones, pero creo que interpretar cada llamada de superconstructor como this = [new object]
y requiriendo que this
estar en definido antes de que se use como y antes de que un constructor regrese sería semánticamente suficiente para lograr los objetivos establecidos. La incapacidad de envolver las llamadas del constructor padre en un try-catch-rethrow
or try/finally
block hace que sea imposible que un constructor de subclase prometa no lanzar algo que el constructor de superclase podría, incluso si la subclase pudiera garantizar ... - Super gato
... que la excepción no pudo ocurrir. También aumenta enormemente la dificultad de encadenar de forma segura los constructores que necesitan adquirir recursos y pasarlos al constructor padre (el constructor hijo necesita ser invocado por un método de fábrica que crea un contenedor para los recursos, invoca al constructor dentro de un try
block y descarta cualquier recurso en el contenedor si el constructor falla. - Super gato
Técnicamente no es el primero línea, sino más bien la primera declaración ejecutable en el constructor. Es perfectamente legal tener comentarios antes de las invocaciones explícitas del constructor. - ADTC
13
Estoy bastante seguro (los que están familiarizados con la especificación de Java intervienen) de que es para evitar que (a) se le permita usar un objeto parcialmente construido, y (b), forzar al constructor de la clase principal 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
}
}
Respondido 22 Jul 09, 23:07
debería Bad1
y Bad2
ampliar Thing
¿allí? - Michael Myers ♦
no estoy de acuerdo con Bad2
as x
se declara en Thing
y simplemente no debe configurarse en ningún otro lugar. Como para Bad1
, seguramente tiene razón, pero algo similar puede suceder cuando el superconstructor invoca un método anulado en la subclase que accede a una variable (aún no inicializada) de la subclase. Entonces, la restricción ayuda a prevenir una parte del problema ... que, en mi humilde opinión, no vale la pena. - maaartinus
@maaartinus la diferencia es que el autor del constructor de superclase tiene la responsabilidad de invocar métodos invalidables. Por lo tanto, es posible diseñar la superclase de manera que siempre tenga un estado consistente, lo que no sería posible si las subclases pudieran usar el objeto antes de que se haya llamado al constructor de la superclase. - Holger
9
Usted preguntó por qué, y las otras respuestas, imo, realmente no dicen por qué está bien llamar al constructor de su super, pero solo si es la primera línea. La razón es que no eres realmente llamar el constructor. En C ++, la sintaxis equivalente es
MySubClass: MyClass {
public:
MySubClass(int a, int b): MyClass(a+b)
{
}
};
Cuando ve la cláusula inicializadora por sí sola así, antes de la llave abierta, sabe 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 hacer que se ejecute algún código (otros constructores) antes de que el constructor realmente comience, antes de que se inicialice cualquier miembro de la subclase. Y de esa forma es poner la "llamada" (p. Ej. super
) en la primera línea. (De manera que super
or this
es un poco antes de la primera llave abierta, aunque la escriba después, porque se ejecutará antes de llegar al punto en que todo esté completamente construido). Cualquier otro código después de la llave abierta (como int c = a + b;
) hace que el compilador diga "oh, ok, no hay otros constructores, entonces podemos inicializar todo". Entonces se ejecuta e inicializa su superclase y sus miembros y todo eso y luego comienza a ejecutar el código después de la llave abierta.
Si, unas líneas más tarde, encuentra algún código que dice "oh, sí, cuando estás construyendo este objeto, aquí están los parámetros que quiero que pases al constructor de la clase base", es demasiado tarde y no ningún sentido. Entonces obtienes un error del compilador.
Respondido 14 Jul 11, 18:07
1. Si los diseñadores de Java quisieran un superconstructor implícito, podrían hacer eso y, lo que es más importante, esto no explica por qué el superconstructor implícito es muy útil. 2. OMI, es su comentario que no tiene ningún sentido no tiene ningún sentido. Recuerdo que necesitaba eso. ¿Puedes probar que hice algo sin sentido? - ola
imagina que necesitas entrar en una habitación. La puerta está cerrada, así que rompes una ventana, alcanzas y te dejas entrar. En el interior, en la mitad de la habitación, encuentras una nota con una llave para que la uses al entrar. Pero ya estás dentro. Del mismo modo, si el compilador está a la mitad de la ejecución de un constructor y se encuentra con "esto es lo que debe hacer con esos parámetros antes de ejecutar el constructor", ¿qué se supone que debe hacer? - Kate Gregory
Si es algo estúpido en realidad, entonces es una analogía incorrecta. Si estoy en condiciones de decidir qué camino tomar, no estoy a mitad de camino. La regla es que la supercall debe ser la primera en el constructor que nos permita romper la ventana (ver muchos ejemplos de warkingaround en la pregunta y respuestas) en lugar de usar la puerta. Entonces, pones todo al revés cuando intentas defender esta regla. Por tanto, la regla debe ser incorrecta. - ola
-1 Esto no refleja cómo se compila realmente el código en Java, las restricciones sobre él o la razón real para diseñar Java de la forma en que es. - Antimonio
6
Por lo tanto, no le impide ejecutar la lógica antes de la llamada a super. Simplemente le impide ejecutar la lógica 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 declaración super.
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;
}
}
Respondido el 18 de junio de 14 a las 10:06
Esto solo funciona si el constructor de la superclase espera un único argumento no nulo: KrishPrabakar
en lugar de elegir hacer super()/this()
como primera declaración, los diseñadores de lenguaje podrían haber optado por aplicar ningún método de instancia o se pueden realizar llamadas a métodos heredados antes de llamar super()/this()
. Entonces, tal vez OP esté preguntando por qué no se hizo de esta manera. - Archit
4
Estoy totalmente de acuerdo, las restricciones son demasiado fuertes. No siempre es posible utilizar un método auxiliar estático (como sugirió Tom Hawtin - tackline) o introducir todos los "cálculos pre-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 verificar esto durante la construcción de cada objeto ralentizaría la ejecución. Yo preferiría un compilador de Java que haga una mejor diferenciación (en lugar de prohibir inconsecuentemente una declaración if pero permitir el operador? Dentro del parámetro), incluso si esto complica la especificación del lenguaje.
Respondido 14 Jul 11, 18:07
Creo que el voto negativo se debe a que no está respondiendo la pregunta, sino que está haciendo comentarios sobre el tema. Estaría bien en un foro, pero SO / SE no es uno :) - ADTC
Excelente ejemplo de las formas en que ?:
El tipo de constructo puede sorprenderte. Estaba pensando mientras leía: "No es imposible --- solo usa una operación ternaria ... Oh. ". - kevin j chase
3
Puede usar bloques inicializadores anónimos para inicializar campos en el niño 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
Respondido 22 Jul 09, 22:07
Pero esto no agrega nada más que simplemente agregar el System.out.println("In initializer")
como la primera línea después de "super ()", ¿verdad? Lo que sería útil sería una forma de ejecutar código. antes al con el futuro bebé esta construido. - svend hansen
En efecto. Si intenta agregar algo, deberá guardar el estado calculado en algún lugar. Incluso si el compilador lo permite, ¿cuál será el almacenamiento temporal? ¿Asignar un campo más solo para la inicialización? Pero esto es un desperdicio de memoria. - ola
Esto es incorrecto. Los inicializadores de instancia se insertan después de que regresa la llamada al constructor principal. - Antimonio
3
Encontré un woraround.
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);
}
}
Respondido 30 Oct 15, 14:10
3
Supongo que hicieron esto para facilitar la vida de las personas que escriben herramientas que procesan código Java y, en menor grado, también de las personas que leen código Java.
Si permites que el super()
or this()
llame para moverse, hay más variaciones para verificar. Por ejemplo, si mueve el super()
or this()
llamar a un condicional if()
podría tener que ser lo suficientemente inteligente como para insertar un implícito super()
en el else
. Es posible que deba saber cómo informar un error si llama super()
dos veces, o usar super()
y this()
juntos. Es posible que deba rechazar las llamadas a métodos en el receptor hasta super()
or this()
se llama y averiguar cuándo se vuelve complicado.
Hacer que todos hicieran este trabajo adicional probablemente parecía un mayor costo que beneficio.
Respondido 08 Feb 18, 09:02
Escribir una gramática sana para la característica sería en sí mismo bastante difícil; tal gramática coincidiría con un árbol de declaraciones donde como máximo un nodo hoja es una llamada explícita a un superconstructor. Puedo pensar en una forma de escribirlo, pero mi enfoque sería bastante loco. - Trejkaz
3
¿Puede dar un ejemplo de código en el que, si el compilador no tuviera esta restricción, sucedería 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 se pudo inicializar 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 ninguno essential1
ni essential2
están correctamente inicializados.
Puede decir que ignorar las excepciones siempre es una mala idea. Bien, aquí hay otro ejemplo:
class Bad extends Good {
Bad(int n) {
for (int i = 0; i < n; i++)
super(i);
}
}
Gracioso, ¿no es así? ¿Cuántos objetos estamos creando en este ejemplo? ¿Uno? ¿Dos? O tal vez nada ...
Permitiendo llamar super()
or this()
en medio de un constructor abriría una caja de Pandora de atroces constructores.
Por otro lado, entiendo la necesidad frecuente de incluir alguna parte estática antes de una llamada a super()
or this()
. Este podría ser cualquier código que no dependa de this
referencia (que, de hecho, ya existe al principio de un constructor, pero no se puede utilizar de forma ordenada hasta super()
or this()
devoluciones) y necesario para realizar dicha llamada. Además, como en cualquier método, existe la posibilidad de que algunas variables locales creadas antes de la llamada a super()
or this()
será necesario después de él.
En tales casos, tiene las siguientes oportunidades:
- Utilice el patrón presentado en esta respuesta, lo que permite eludir la restricción.
- Espere a que el equipo de Java permita la
super()
y prethis()
código. Puede hacerse imponiendo una restricción sobre dóndesuper()
orthis()
puede ocurrir en un constructor. En realidad, incluso el compilador actual es capaz de distinguir casos buenos y malos (o potencialmente malos) con el grado suficiente para permitir de forma segura la adición de código estático al comienzo de un constructor. De hecho, suponga quesuper()
ythis()
retornothis
referencia 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 "es posible que la variable x no se haya inicializado", podría hacerlo en this
variable, haciendo sus comprobaciones en ella como en cualquier otra variable local. La única diferencia es this
no se puede asignar por ningún otro medio que no sea super()
or this()
llamar (y, como de costumbre, si no hay tal llamada en un constructor, super()
se inserta implícitamente por el compilador al principio) y es posible que no se asigne dos veces. En caso de duda (como en la primera get()
, Donde x
en realidad siempre está asignado), el compilador podría devolver un error. Eso sería mejor que simplemente devolver un error en cualquier constructor donde haya algo excepto un comentario antes super()
or this()
.
Respondido 30 Jul 18, 08:07
esto es tarde, pero también puede usar el patrón de fábrica. Haga que los constructores sean privados. Crea métodos estáticos asociados con los constructores. Llamemos a la clase Foo, 2 constructores, Foo () y Foo (int i), y los métodos estáticos que la construyen, createFoo () y createFoo (int i). Luego reemplace esto () con Foo.createFoo (). Por lo tanto, puede hacer cosas en createFoo (int i) y, por último, hacer Foo.createFoo. O cualquier otro pedido. Es como un patrón de diseño de fábrica, pero no. - Jorge Javier
2
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 necesite realizar es independiente y posiblemente un requisito previo de cualquier inicialización realizada por la subclase. Por lo tanto, primero debe completar su ejecución.
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();
}
}
El resultado de este programa es:
Inside A's constructor
Inside B's constructor
Inside C's constructor
Respondido el 05 de junio de 17 a las 14:06
En este ejemplo, hay un constructor predeterminado en cada clase y, por lo tanto, no es necesario llamar de emergencia al método super (..., ...) en la subclase - mohsen abasi
1
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 InfoRunnable<T>
con un método:
public T run(Object... args);
Y si necesito hacer algo antes de pasarlo al constructor, simplemente hago esto:
super(new InfoRunnable<ThingToPass>() {
public ThingToPass run(Object... args) {
/* do your things here */
}
}.run(/* args here */));
Respondido 22 Jul 16, 18:07
1
En realidad, super()
es la primera declaración de un constructor porque para asegurarse de que su superclase esté completamente formada antes de que se construya la subclase. Incluso si no tienes super()
en su primera declaración, el compilador lo agregará por usted.
Respondido 08 Feb 18, 09:02
1
Eso es porque su constructor depende de otros constructores. Para que su constructor funcione correctamente, es necesario que otro constructor funcione correctamente, lo cual es dependiente. Es por eso que es necesario verificar primero los constructores dependientes que llamaron por this () o super () en su constructor. Si otros constructores que llamaron por this () o super () tienen un problema, entonces qué sentido tiene ejecutar otras declaraciones porque todas fallarán si falla el constructor llamado.
Respondido 04 Oct 18, 15:10
0
Tldr:
Las otras respuestas han abordado el "por qué" de la pregunta. Proporcionaré un corte alrededor de esta limitación:
La idea básica es secuestrar al super
declaración con sus declaraciones incrustadas. Esto se puede hacer disfrazando sus declaraciones como expresiones.
Tsdr:
Considere que queremos hacer Statement1()
a Statement9()
antes de que llamemos 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. Entonces, 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 el la clase padre debe tener un constructor que tome al menos un argumento para que podamos colarse en nuestra declaración como 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 habernos automatizado este proceso. Simplemente habían elegido no hacerlo.
contestado el 23 de mayo de 17 a las 13:05
En el segundo bloque de código, super(F(), _2, _3);
debiera ser super(F(_1), _2, _3);
- ADTC
"la clase padre debe tener un constructor que tome al menos un argumento" no es cierto - simplemente cree otro constructor en su propia clase que tome un argumento. - Alejandro Dubinsky
0
Antes de que pueda construir un objeto hijo, debe crear su objeto padre. Como sabes cuando escribes una clase como esta:
public MyClass {
public MyClass(String someArg) {
System.out.println(someArg);
}
}
pasa al siguiente (extender y super simplemente están ocultos):
public MyClass extends Object{
public MyClass(String someArg) {
super();
System.out.println(someArg);
}
}
Primero creamos un Object
y luego extender este objeto a MyClass
. No podemos crear MyClass
antes de Object
. La regla simple es que se debe llamar al constructor principal antes que al constructor secundario. Pero sabemos que las clases pueden tener más de un constructor. Java nos permite elegir un constructor que se llamará (o será super()
or super(yourArgs...)
). Entonces, cuando escribes super(yourArgs...)
redefine el constructor que será llamado para crear un objeto padre. No puedes ejecutar otros métodos antes super()
porque el objeto aún no existe (pero después super()
se creará un objeto y podrá hacer lo que quiera).
Entonces, ¿por qué entonces no podemos ejecutar this()
después de cualquier método? Como sabes this()
es el constructor de la clase actual. También podemos tener diferentes números de constructores en nuestra clase y llamarlos como this()
or this(yourArgs...)
. Como dije, cada constructor tiene un método oculto. super()
. Cuando escribimos nuestra costumbre super(yourArgs...)
nosotros quitamos super()
a super(yourArgs...)
. También cuando definimos this()
or this(yourArgs...)
también eliminamos nuestro super()
en el constructor actual porque si super()
estaban con this()
en el mismo método, crearía más de un objeto principal. Por eso se imponen las mismas reglas para this()
método. Simplemente retransmite la creación del objeto padre a otro constructor hijo y ese constructor llama super()
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 dicen otros, puede 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 puede ejecutar código como este porque su 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 puede 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);
}
}
respondido 29 nov., 16:13
Entonces, ¿cree que ninguna de las respuestas existentes y altamente votadas lo explica lo suficientemente bien? - Tomás Weller
Yo no lo dije. ¿Por qué piensas eso? - Oleksandr
Porque respondes una pregunta que tiene 7 años y ya tiene muchas respuestas. ¿Cuál es tu razón para publicar otro? - Tomás Weller
Publiqué mi versión de la respuesta con ejemplos detallados. Creo que no hay una respuesta perfecta para todos. Alguien piensa que la respuesta "A" es mejor para comprender; otro piensa que la respuesta "B" es mejor para comprender. Es por eso que tenemos tantos tutoriales / libros / respuestas diferentes sobre los mismos temas. Publiqué mi respuesta porque creo que mi respuesta será más comprensible para una pequeña parte de las personas. - Oleksandr
0
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 de z. z no podría obtener el valor correcto.
respondido 02 mar '17, 06:03
0
La pregunta de por qué Java hace esto ya ha sido respondida, pero como me topé con esta pregunta con la esperanza de encontrar una mejor alternativa a la de una sola línea, por la presente compartiré mi solución alternativa:
public class SomethingComplicated extends SomethingComplicatedParent {
private interface Lambda<T> {
public T run();
}
public SomethingComplicated(Settings settings) {
super(((Lambda<Settings>) () -> {
// My modification code,
settings.setting1 = settings.setting2;
return settings;
}).run());
}
}
Llamar a una función estática debería funcionar mejor, pero lo usaría si insisto en tener el código "dentro" del constructor, o si tengo que alterar múltiples parámetros y encontrar que definir muchos métodos estáticos es malo para la legibilidad.
Respondido el 29 de Septiembre de 20 a las 02:09
0
El objetivo principal de agregar super () en los constructores de subclase es que el trabajo principal del compilador es hacer una conexión directa o indirecta de todas las clases con la clase Object, por eso el compilador verifica si hemos proporcionado el super (parametrizado), el compilador no asume ninguna responsabilidad. de modo que todo el miembro de la instancia se inicialice de Object a las subclases.
respondido 07 nov., 20:10
No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas java constructor or haz tu propia pregunta.
odio, odio, odio, odio tanto por esto. ¿Por qué no puede haber una excepción de "objeto aún no construido", de modo que no tenga que meter todo en una sola línea? - Carson Myers
Buena pregunta. He comenzado una similar en valjok.blogspot.com/2012/09/… y programmers.exchange donde muestro que hay casos en los que los subcampos deben inicializarse antes del super (). De modo que la función aumenta la complejidad de hacer las cosas, mientras que no está claro si los impactos positivos con respecto a la "seguridad del código" sobrepasan los negativos. Sí, hay consecuencias negativas de super siempre primero. Sorprende que nadie mencione esto. Creo que esto es algo conceptual y debe preguntarse en programadores.exchange - Val
Gracias por describir claramente cómo solucionar esta restricción. - Grilse
La peor parte es que esto es puramente una restricción de Java. A nivel de código de bytes, no existe tal restricción. - Antimony
Bueno, sería imposible tener esa restricción a nivel de código de bytes; todos los ejemplos en esta publicación violarían tal restricción, incluso los que incluyen toda la lógica en una sola expresión. - celticminstrel