2009-07-27 9 views
6

tengo este código¿Por qué el orden de las declaraciones es importante para los inicializadores estáticos?

private static Set<String> myField; 

static { 
    myField = new HashSet<String>(); 
    myField.add("test"); 
} 

y funciona. Pero cuando cambio el orden, recibo un error de referencia directa.

static { 
    myField = new HashSet<String>(); 
    myField.add("test"); // illegal forward reference 
} 

private static Set<String> myField; 

estoy un poco sorprendido, no esperaba algo así de Java. :)

¿Qué sucede aquí? ¿Por qué es importante el orden de las declaraciones? ¿Por qué funciona la asignación, pero no la llamada al método?

Respuesta

10

Antes que nada, veamos qué es una "referencia directa" y por qué es mala. Una referencia directa es una referencia a una variable que aún no se ha inicializado, y no está confinada solo a incializadores estáticos. Estos son malos simplemente porque, de ser permitidos, nos darían resultados inesperados. Eche un vistazo a este código:

public class ForwardRef { 
    int a = b; // <--- Illegal forward reference 
    int b = 10; 
} 

¿Qué debe ser j cuando se inicializa esta clase? Cuando se inicializa una clase, las inicializaciones se ejecutan en orden de primero a último encontrado. Por lo tanto, uno esperaría que la línea

a = b; 

a ejecutar antes:

b = 10; 

Con el fin de evitar este tipo de problemas, los diseñadores de Java completamente no permitidos tales usos de referencias hacia delante.

EDITAR

este comportamiento se especifica mediante section 8.3.2.3 of Java Language Specifications:

La declaración de un miembro debe aparecer antes de ser usado sólo si el miembro es una instancia de campo (respectivamente estática) de una clase o interfaz C y se cumplen todas las condiciones siguientes:

  • El uso se produce en una instancia (respectiva ly static) inicializador variable de C o en un inicializador instancia (respectivamente estático) de C.

  • El uso no está en el lado izquierdo de una tarea.

  • C es la clase o interfaz más interna que encierra el uso.

Se produce un error de tiempo de compilación si no se cumple alguno de los tres requisitos anteriores.

+0

OK, entiendo. Pero después de la asignación inicial, myField * está * inicializado. ¿Por qué aún no puedo llamar al método add? –

+0

Si esos tres requisitos no existieran, podría crear una referencia directa implícita utilizando variables locales en el inicializador, ¿no? ¿Es ese el motivo de estas restricciones? –

+0

ths JLS dice: "... Estas restricciones están diseñadas para capturar, en tiempo de compilación, inicializaciones circulares o malformadas ..." –

1

En Java, todos los inicializadores, estáticos o no, se evalúan en el orden en que aparecen en la definición de clase.

+2

Pero creo que la pregunta es * ¿por qué?, ¿No es así? –

+0

¿Por qué eso prohíbe llamar al método add pero no a la asignación? –

0

creo que la llamada al método es problemático porque el compilador no puede determinar qué add() método a utilizar sin un tipo de referencia para myField.

En tiempo de ejecución, el método utilizado estará determinado por el tipo de objeto, pero el compilador solo conoce el tipo de referencia.

+0

No creo que ese sea el problema, porque con los métodos virtuales el el compilador casi nunca puede enlazar el método en tiempo de compilación. Ahí es donde se usa el VMT: http://en.wikipedia.org/wiki/Virtual_method_table –

1

Consulte las reglas para referencias futuras en the JLS. No se puede utilizar adelante referencias si:

  • El uso se produce en una instancia (respectivamente estática) inicializador variable del C o en una instancia (respectivamente estática) inicializador de C.
  • El uso no está en la mano izquierda lado de una tarea.
  • El uso es a través de un nombre simple.
  • C es la clase o interfaz más interna que encierra el uso.

Dado que todo esto es válido para su ejemplo, la referencia directa es ilegal.

2

probar esto:

class YourClass { 
    static { 
     myField = new HashSet<String>(); 
     YourClass.myField.add("test"); 
    } 

    private static Set<String> myField; 
} 

se debe compilar sin errores según la JLS ...
(en realidad no ayudar, o?)

+0

No he probado esto, pero esto todavía infringe la "regla de la izquierda" en el JLS. Vea mi respuesta a continuación para saber por qué. – rtperson

+0

Lo he probado y funciona. Ahora ya no entiendo nada ... –

+1

@DR: esta es una característica del compilador. La reflexión se puede usar para sortear muchas verificaciones de compilación, p. llamando a un método privado desde fuera de la clase. –

1

Para más detalles sobre la respuesta de DFA:

Creo que lo que le está haciendo tropezar es la regla del "lado izquierdo" en el segundo punto en JLS 8.2.3.2. En su inicialización, myField está en el lado izquierdo. En su llamada para agregar, está en el lado derecho. El código aquí es implícito:

boolean result = myField.add('test') 

No está evaluando el resultado, pero el compilador todavía actúa como si estuviera allí. Es por eso que su inicialización pasa mientras su llamada a add() falla.

En cuanto a por qué esto es así, no tengo ni idea. Puede ser conveniente para los desarrolladores de JVM, por lo que sé.

+0

Si ese es el caso, ¿no debería funcionar un método nulo? –

+0

Bueno, no, por exactamente la misma razón. Una llamada que devuelve nulo está todavía en el lado derecho, ya que no está asignando en ella. Simplemente le has dicho al compilador que la función no tiene ningún resultado, pero eso no cambia que sea una función. – rtperson

Cuestiones relacionadas