programming - what is an instance java




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

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

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?


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

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

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

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.


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.


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


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.







constructor