studio propiedades programacion móviles desarrollo curso aplicaciones java compiler-construction

propiedades - ¿Por qué compila este código Java?



textarea java netbeans (14)

En el alcance del método o clase, la siguiente línea compila (con advertencia):

int x = x = 1;

En el ámbito de la clase, donde las variables obtienen sus valores predeterminados , el siguiente da el error de "referencia no definida":

int x = x + 1;

¿No es el primero que x = x = 1 debería terminar con el mismo error de "referencia indefinida"? O tal vez la segunda línea int x = x + 1 debería compilar? O hay algo que me falta?


tl; dr

Para los campos , int b = b + 1 es ilegal porque b es una referencia forward ilegal a b . De hecho, puede solucionar esto escribiendo int b = this.b + 1 , que compila sin quejas.

Para las variables locales , int d = d + 1 es ilegal porque d no se inicializa antes del uso. Este no es el caso para 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 fracasará, pero el segundo tendrá éxito, debido a la diferencia en 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.

Utilizaremos este programa de prueba a lo largo de:

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 error de illegal forward reference .
La declaración de d no es válida y falla con una variable d might not have been initialized error variable d might not have been initialized .

El hecho de que estos errores sean diferentes debería indicar que los motivos 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 , Ámbito 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 tipos anidados.
  • Las expresiones de inicialización para variables de instancia pueden usar el nombre simple de cualquier variable estática declarada en o heredada por la clase, incluso una declaración 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 se restringe, aunque estas variables de instancia están dentro del alcance. Ver §8.3.2.3 para las reglas precisas que gobiernan la referencia directa a 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 condiciones siguientes:

  • El uso se produce 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.

En realidad, puede consultar 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 compilar La especificación Java dice "las restricciones anteriores están diseñadas para capturar, en tiempo de compilación, inicializaciones circulares o malformadas".

¿A qué se reducen 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 a, (c) la referencia es una nombre simple (sin calificadores como this. ) y (d) no se está accediendo desde dentro de una clase interna. Por lo 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): se está asignando la referencia a, por lo que es legal referirse a a antes de la declaración completa.

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 construcción extraña 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 previenen que int a = a + 1 se compile exitosamente.

Observe que la declaración de campo int b = (b = 1) + b no podrá compilarse, porque la b final sigue siendo una referencia directa ilegal.

Variables locales

Las declaraciones de variables locales se rigen por JLS §14.4 , Declaraciones de declaraciones 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 declarante 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 int d = d + 1; ¿compilar?

La razón se debe a la regla de Java sobre la asignación definitiva ( JLS §16 ). La asignación definitiva básicamente dice que cada acceso a una variable local debe tener una asignación previa a esa variable, y el compilador de Java verifica bucles y bifurcaciones para asegurarse de que la asignación siempre ocurra antes de cualquier uso (esta es la razón por la cual la asignación definitiva 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 produce un error en tiempo de compilación.

En int d = d + 1; , el acceso a d se resuelve con la variable local fine, pero como d no se ha asignado antes de acceder a d , el compilador emite un error. En int c = c = 1 , c = 1 ocurre primero, lo que asigna c , y luego c se inicializa al resultado de esa asignación (que es 1).

Tenga en cuenta que debido a reglas de asignación definidas, la declaración de variable local int d = (d = 1) + d; compilará con éxito (a diferencia de la declaración de campo int b = (b = 1) + b ), porque d se asigna definitivamente en el momento en que se alcanza la d final.


El segundo int x=x=1 es compilar porque estás asignando el valor a la x pero en otro caso int x=x+1 aquí la variable x no se inicializa, Remember in java local variable no se inicializa al valor predeterminado. Nota: si es ( int x=x+1 ) también en el alcance de la clase, también dará error de compilación ya que la variable no se crea.


En int x = x + 1; agregas 1 a x, entonces ¿cuál es el valor de x , aún no está creado?

Pero en int x=x=1; compilará sin error porque asigna 1 a x .


En Java o en cualquier idioma moderno, la asignación viene de la derecha.

Supongamos que si tiene dos variables xey,

int z = x = y = 5;

Esta afirmación es válida y así es como el compilador las divide.

y = 5; x = y; z = x; // which will be 5

Pero en tu caso

int x = x + 1;

El compilador dio una excepción porque se divide así.

x = 1; // oops, it isn''t declared because assignment comes from the right.


En el segundo fragmento de código, x se usa antes de su declaración, mientras que en el primer fragmento de código solo se asigna dos veces, lo cual no tiene sentido, pero es válido.


Es más o menos equivalente a:

int x; x = 1; x = 1;

En primer lugar, int <var> = <expression>; siempre es equivalente a

int <var>; <var> = <expression>;

En este caso, tu expresión es x = 1 , que también es una declaración. x = 1 es una declaración válida, ya que la var x ya ha sido declarada. También es una expresión con el valor 1, que luego se asigna a x nuevamente.


La línea de código no se compila con una advertencia debido a cómo funciona realmente el código. Cuando ejecuta el código int x = x = 1 , Java primero crea la variable x , como se define. Luego ejecuta el código de asignación ( x = 1 ). Como x ya está definido, el sistema no tiene ningún error al establecer x a 1. Esto devuelve el valor 1, porque ese es ahora el valor de x . Por lo tanto, x ahora está configurado como 1.
Java básicamente ejecuta el código como si fuera esto:

int x; x = (x = 1); // (x = 1) returns 1 so there is no error

Sin embargo, en su segundo fragmento de código, int x = x + 1 , la instrucción + 1 requiere que se defina x , que para entonces no lo es. Como las declaraciones de asignación siempre significan que el código a la derecha de = se ejecuta primero, el código fallará porque x no está definido. Java correría el código así:

int x; x = x + 1; // this line causes the error because `x` is undefined


Las declaraciones de lectura de Complier de derecha a izquierda y diseñamos hacer lo contrario. Por eso fue molesto al principio. Haga que este sea un habito para leer enunciados (código) de derecha a izquierda, no tendrá ese problema.


Su primer fragmento de código contiene un segundo = lugar de un más. Esto se compilará en cualquier lugar, mientras que la segunda pieza de código no se compilará en ningún lugar.


Vamos a descomponerlo paso a paso, asociativo correcto

int x = x = 1

x = 1 , asigne 1 a una variable x

int x = x , asigna lo que x es a sí mismo, como un int. Como x se asignó previamente como 1, conserva 1, aunque de forma redundante.

Eso compila bien.

int x = x + 1

x + 1 , agrega uno a una variable x. Sin embargo, x no está definido, esto causará un error de compilación.

int x = x + 1 , así esta línea compila errores ya que la porción derecha de los iguales no compilará agregando uno a una variable no asignada


x no se inicializa en x = x + 1 ;.

El lenguaje de programación Java está tipado estáticamente, lo que significa que todas las variables deben declararse antes de que puedan ser utilizadas.

Ver tipos de datos primitivos


int x = x = 1; no es igual a:

int x; x = 1; x = x;

javap nos ayuda de nuevo, estas son instrucciones de JVM generadas para este código:

0: iconst_1 //load constant to stack 1: dup //duplicate it 2: istore_1 //set x to constant 3: istore_1 //set x to constant

más como:

int x = 1; x = 1;

Aquí no hay razón para lanzar un error de referencia no definido. Ahora hay uso de variable antes de su inicialización, por lo que este código cumple completamente con la especificación. De hecho, no hay uso de variable en absoluto , solo asignaciones. Y el compilador JIT irá aún más lejos, eliminará tales construcciones. Al decir honestamente, no entiendo cómo este código está conectado a la especificación de JLS de inicialización y uso de variables. Sin uso no hay problemas ;)

Por favor, corrige si estoy equivocado. No puedo entender por qué otras respuestas, que se refieren a muchos párrafos de JLS, reúnen tantas ventajas. Estos párrafos no tienen nada en común con este caso. Solo dos asignaciones en serie y nada más.

Si escribimos:

int b, c, d, e, f; int a = b = c = d = e = f = 5;

es igual a:

f = 5 e = 5 d = 5 c = 5 b = 5 a = 5

La expresión más a la derecha simplemente se asigna a las variables una a una, sin ninguna recursión. Podemos confundir las variables de cualquier forma que nos guste:

a = b = c = f = e = d = a = a = a = a = a = e = f = 5;


int x = x + 1;

compila con éxito en Visual Studio 2008 con advertencia

warning C4700: uninitialized local variable ''x'' used`


int x = x = 1;

es equivalente a

int x = 1; x = x; //warning here

mientras en

int x = x + 1;

primero tenemos que calcular x+1 pero el valor de x no se conoce, por lo que se obtiene un error (el compilador sabe que no se conoce el valor de x)