2011-11-11 13 views
13

Miremos el siguiente fragmento de código en Java.Una llamada a un método estático dentro de la lista de parámetros de super() es válida en Java. ¿Por qué?

package trickyjava; 

class A 
{ 
    public A(String s) 
    { 
     System.out.println(s); 
    } 
} 

final class B extends A 
{ 
    public B() 
    { 
     super(method());  // Calling the following method first.  
    } 

    private static String method() 
    { 
     return "method invoked"; 
    } 
} 

final public class Main 
{ 
    public static void main(String[] args) 
    { 
     B b = new B(); 
    } 
} 

Por convención, el super() constructor en Java debe ser la primera instrucción en el cuerpo del constructor correspondiente. En el código anterior, estamos llamando al método estático en la lista de parámetros del constructor super() en sí super (método());.


Esto significa que en la llamada a super en el constructor B(), está siendo llamado antes de la llamada a super se hizo un método! Esto debería estar prohibido por el compilador pero funciona bien. Esto es algo equivalente a las siguientes declaraciones.

String s = method(); 
super(s); 

Sin embargo, es ilegal que causa un error en tiempo de compilación que indica que "llamada a super debe ser primera declaración en el constructor". ¿Por qué? y por qué es equivalente super (método()); es válido y el compilador ya no se queja?

+0

Ver http://stackoverflow.com/questions/1168345/why-does-this-and-super-have-to-be-the-first- statement-in-a-constructor – wannik

+4

'super' ES la primera instrucción en el constructor, a pesar de no ser llamado primero. La especificación establece que 'La primera declaración de un cuerpo constructor puede ser una invocación explícita de otro constructor ...', no que se deba llamar primero. –

Respuesta

10

La clave aquí es el modificador static. Los métodos estáticos están relacionados con la clase , los métodos de instancia (métodos normales) están vinculados a un objeto (instancia de clase). El constructor inicializa un objeto de una clase, por lo tanto, la clase ya debe haberse cargado por completo. Por lo tanto, no es problema llamar a un método estático como parte del constructor.

La secuencia de eventos para cargar una clase y crear un objeto es de esta manera:

  1. clase de carga
  2. inicializar variables estáticas
  3. crear el objeto
  4. inicializar el objeto < - con el constructor
  5. objeto ahora está listo para usar

(simplificado *)

En el momento en que se llama al constructor de objetos, los métodos estáticos y las variables están disponibles.

Piensa en la clase y sus miembros static como modelo para los objetos de esa clase. Solo puedes crear los objetos cuando el blueprint ya está allí.

El constructor también se denomina inicializador. Si lanza una excepción desde un constructor e imprime el seguimiento de la pila, notará que se llama <init> en el marco de la pila. Los métodos de instancia solo pueden invocarse una vez que se ha construido un objeto. No es posible usar un método de instancia como el parámetro para la llamada super(...) en su constructor.

Si crea varios objetos de la misma clase, los pasos 1 y 2 suceden solo una vez.

(* inicializadores estáticos y inicializadores de instancia dejado fuera para mayor claridad)

+1

En realidad, podría llamar a un método de instancia de otra clase, o incluso esta, si obtuvo la instancia de otra parte (por ejemplo, un método estático o un campo estático público, o un parámetro para el método init). Simplemente no puedes usar "esto". –

2

Una llamada a super es imprescindible en java para permitir que los padres se inicien antes de cualquier cosa con el inicio de clase infantil.

En el caso anterior, si java permite String s= method(); antes de la llamada a super, se abre la puerta de inundación de las cosas que se pueden hacer antes de una llamada a super. eso arriesgaría muchas cosas, esencialmente eso permite que se use una clase a medio cocer. Lo cual no está permitido. Permitiría que cosas como el estado del objeto (algunas de las cuales pueden pertenecer al padre) se modifiquen antes de crearse correctamente.

En el caso de la llamada super(method());, seguimos cumpliendo con la política de completar la inicialización principal antes del hijo. y solo podemos usar un miembro estático, y el miembro estático de clases secundarias está disponible antes de que se creen objetos secundarios de todos modos. entonces el método está disponible y se puede llamar.

3

Esta es una situación donde la sintaxis java oculta lo que realmente está pasando, y C# lo hace un poco más claro.

En C# su B se vería como

class B : A { 
    public B() : base(method()) { 
    } 

    private static String method() { 
     return "method invoker"; 
    } 
} 

Aunque los lugares sintaxis de Java super(method) dentro del constructor en realidad no es invocó allí: Toda la inicialización de los padres se ejecuta antes de el constructor de la subclase. El código C# muestra esto un poco más claro; al colocar super(method()) en la primera línea del constructor java simplemente le está diciendo a java que use el constructor parametrizado de la superclase en lugar de la versión sin parámetros; de esta forma puede pasar variables al constructor padre y se usarán en la inicialización de los campos de nivel padre antes de se ejecuta el código de constructor de su hijo.

La razón de que super(method()) es válido (como la primera línea en un constructor java) es porque method() está siendo cargado con los elementos estáticos - antes que los no estáticos, incluyendo los constructores - lo que le permite ser llamado no solo antes de B(), sino también antes de A(String).Al decir

public B() { 
    String s = method(); 
    super(s); 
} 

usted está diciendo al compilador de Java para inicializar el objeto estupenda con el constructor por defecto (ya que la llamada a super() no es la primera línea) y ya está listo para iniciar la subclase, pero el el compilador se confunde cuando ve que está intentando inicializar con super(String) después de que super() ya se haya ejecutado.

5

Sí, el control de la especificación JVM (aunque es cierto que uno antiguo):

En el método de instancia init, no se hace referencia a "este" (incluida la referencia implícita de un retorno) puede ocurrir antes de que se haya producido una llamada a otro método de inicio en la misma clase o a un método de inicio en la superclase.

Esta es realmente la única restricción real, por lo que puedo ver.

4

El objetivo de requerir que el superconstructor sea invocado primero es asegurar que el "súper objeto" se inicialice completamente antes de ser utilizado (no llega a aplicar esto porque el super constructor puede filtrar this, pero eso es otro importar).

Llamar a un método no estático en this permitiría que el método vea campos no inicializados y, por lo tanto, está prohibido. Un método estático solo puede ver estos campos si se pasa this como argumento. Dado que el acceso a this y super es ilegal en las expresiones de invocación de super constructor, y la llamada a super sucede antes de la declaración de cualquier variable que apunte a this, permitiendo llamadas a métodos estáticos en expresiones de invocación de super constructor es seguro.

También es útil, porque permite calcular los argumentos para el superconstructor de una manera arbitrariamente compleja. Si no se permitieran llamadas a métodos estáticos, sería imposible usar declaraciones de flujo de control en dicho cálculo. Algo tan simple como:

class Sub extends Super { 
    Sub(Integer... ints) { 
     super(Arrays.asList(ints)); 
    } 
} 

sería imposible.

0

OK ... creo, este podría ser relevante que, si llamamos a un miembro con Super, primero intenta invocar en super clase y si no encuentra el mismo, entonces intentará invocar lo mismo en la subclase.

PD: me corrija si me equivoco

Cuestiones relacionadas