tl; dr
Para los campos , int b = b + 1
es ilegal porque b
es una referencia hacia adelante ilegal a b
. De hecho, puede solucionar esto escribiendo int b = this.b + 1
, que se compila sin quejas.
Para las variables locales , int d = d + 1
es ilegal porque d
no se inicializa antes de su uso. Este no es el caso de los campos, que siempre se inicializan por defecto.
Puede ver la diferencia al intentar compilar
int x = (x = 1) + x;
como una declaración de campo y como una declaración de variable local. El primero fallará, pero el segundo tendrá éxito debido a la diferencia de semántica.
Introducción
En primer lugar, las reglas para los inicializadores de variables locales y de campo son muy diferentes. Entonces, esta respuesta abordará las reglas en dos partes.
Usaremos este programa de prueba en todo momento:
public class test {
int a = a = 1;
int b = b + 1;
public static void Main(String[] args) {
int c = c = 1;
int d = d + 1;
}
}
La declaración de b
no es válida y falla con un illegal forward reference
error.
La declaración de d
no es válida y falla con un variable d might not have been initialized
error.
El hecho de que estos errores sean diferentes debería indicar que las razones de los errores también son diferentes.
Campos
Los inicializadores de campo en Java se rigen por JLS §8.3.2 , Inicialización de campos.
El alcance de un campo se define en JLS §6.3 , Alcance de una declaración.
Las reglas relevantes son:
- El alcance de una declaración de un miembro
m
declarado o heredado por un tipo de clase C (§8.1.6) es el cuerpo completo de C, incluidas las declaraciones de tipo anidado.
- Las expresiones de inicialización, por ejemplo, las variables, pueden usar el nombre simple de cualquier variable estática declarada o heredada por la clase, incluso una cuya declaración se produzca textualmente más tarde.
- El uso de variables de instancia cuyas declaraciones aparecen textualmente después del uso a veces está restringido, aunque estas variables de instancia están dentro del alcance. Consulte §8.3.2.3 para conocer las reglas precisas que rigen la referencia directa a las variables de instancia.
§8.3.2.3 dice:
La declaración de un miembro debe aparecer textualmente antes de que se use solo si el miembro es un campo de instancia (respectivamente estático) de una clase o interfaz C y se cumplen todas las siguientes condiciones:
- El uso ocurre en un inicializador de variable de instancia (respectivamente estático) de C o en un inicializador de instancia (respectivamente estático) de C.
- El uso no está en el lado izquierdo 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.
De hecho, puede hacer referencia a los campos antes de que se hayan declarado, excepto en ciertos casos. Estas restricciones están destinadas a evitar códigos como
int j = i;
int i = j;
de la compilación. La especificación de Java dice que "las restricciones anteriores están diseñadas para detectar, en tiempo de compilación, inicializaciones circulares o con formato incorrecto".
¿A qué se reducen realmente estas reglas?
En resumen, las reglas básicamente dicen que debe declarar un campo antes de una referencia a ese campo si (a) la referencia está en un inicializador, (b) la referencia no se está asignando, (c) la referencia es un nombre simple (sin calificadores como this.
) y (d) no se está accediendo desde dentro de una clase interna. Por tanto, una referencia directa que satisfaga las cuatro condiciones es ilegal, pero una referencia directa que falla en al menos una condición está bien.
int a = a = 1;
compila porque viola (b): la referencia a la que a
se está asignando, por lo que es legal hacer referencia a
antes de a
la declaración completa de '.
int b = this.b + 1
también compila porque viola (c): la referencia this.b
no es un nombre simple (está calificado con this.
). Esta extraña construcción todavía está perfectamente bien definida, porque this.b
tiene el valor cero.
Entonces, básicamente, las restricciones en las referencias de campo dentro de los inicializadores impiden que int a = a + 1
se compile correctamente.
Observe que la declaración de campo int b = (b = 1) + b
se fallan para compilar, porque la final b
es todavía una referencia ilegal hacia adelante.
Variables locales
Las declaraciones de variables locales se rigen por JLS §14.4 , Declaraciones de declaración de variables locales.
El alcance de una variable local se define en JLS §6.3 , Alcance de una declaración:
- El alcance de una declaración de variable local en un bloque (§14.4) es el resto del bloque en el que aparece la declaración, comenzando con su propio inicializador e incluyendo cualquier otro declarador a la derecha en la declaración de declaración de variable local.
Tenga en cuenta que los inicializadores están dentro del alcance de la variable que se declara. Entonces, ¿por qué no se int d = d + 1;
compila?
La razón se debe a la regla de Java sobre la asignación definitiva ( JLS §16 ). La asignación definida básicamente dice que cada acceso a una variable local debe tener una asignación anterior a esa variable, y el compilador de Java verifica los bucles y las ramas para garantizar que la asignación siempre ocurra antes de cualquier uso (esta es la razón por la que la asignación definitiva tiene una sección de especificación completa dedicada lo). La regla básica es:
- Para cada acceso de una variable local o campo final en blanco
x
, x
debe asignarse definitivamente antes del acceso, o se producirá un error en tiempo de compilación.
En int d = d + 1;
, el acceso a d
se resuelve bien a la variable local, pero como d
no se ha asignado antes d
se accede, el compilador emite un error. En int c = c = 1
, c = 1
ocurre primero, que asigna c
, y luego c
se inicializa con el resultado de esa asignación (que es 1).
Tenga en cuenta que debido a las reglas de asignación definidas, la declaración de la variable local int d = (d = 1) + d;
se compilará correctamente (a diferencia de la declaración de campo int b = (b = 1) + b
), porque d
definitivamente se asigna cuando d
se alcanza la final .
static
en la variable de alcance de clase, como enstatic int x = x + 1;
, ¿obtendrá el mismo error? Porque en C # marca la diferencia si es estático o no estático.