java - pseint - tipos de variables en programacion
¿Por qué intentar imprimir una variable no inicializada no siempre genera un mensaje de error? (6)
Algunos pueden encontrarlo similar a la pregunta SO ¿Las variables finales de Java tendrán valores predeterminados? pero esa respuesta no resuelve completamente esto, ya que esa pregunta no imprime directamente el valor de x dentro del bloque de inicializador de instancia.
El problema surge cuando intento imprimir x directamente dentro del bloque de inicializador de la instancia, mientras le asigno un valor a x antes del final del bloque:
Caso 1
class HelloWorld {
final int x;
{
System.out.println(x);
x = 7;
System.out.println(x);
}
HelloWorld() {
System.out.println("hi");
}
public static void main(String[] args) {
HelloWorld t = new HelloWorld();
}
}
Esto genera un error de tiempo de compilación que indica que la variable x podría no haberse inicializado.
$ javac HelloWorld.java
HelloWorld.java:6: error: variable x might not have been initialized
System.out.println(x);
^
1 error
Caso 2
En lugar de imprimir directamente, estoy llamando a una función para imprimir:
class HelloWorld {
final int x;
{
printX();
x = 7;
printX();
}
HelloWorld() {
System.out.println("hi");
}
void printX() {
System.out.println(x);
}
public static void main(String[] args) {
HelloWorld t = new HelloWorld();
}
}
Esto se compila correctamente y da salida
0
7
hi
¿Cuál es la diferencia conceptual entre los dos casos?
Al leer el JLS, la respuesta parece estar en la sección 16.2.2 :
Se asigna definitivamente un campo de miembro
final
blancoV
(y además no está definitivamente sin asignar) antes del bloque (§14.2) que es el cuerpo de cualquier método en el alcance deV
y antes de la declaración de cualquier clase declarada dentro del alcance deV
Esto significa que cuando se llama a un método, el campo final se asigna a su valor predeterminado 0 antes de invocarlo, por lo que cuando lo referencia dentro del método, se compila correctamente e imprime el valor 0.
Sin embargo, cuando accede al campo fuera de un método, se considera no asignado, de ahí el error de compilación. El siguiente código tampoco se compilará:
public class Main {
final int x;
{
method();
System.out.println(x);
x = 7;
}
void method() { }
public static void main(String[] args) { }
}
porque:
V
se asigna [un] antes que cualquier otra instrucciónS
del bloque si f se asigna [un] después de la instrucción que precede inmediatamente aS
en el bloque.
Como el campo final
x
no está asignado antes de la invocación del método, sigue sin estar asignado después de él.
Esta nota en el JLS también es relevante:
Tenga en cuenta que no hay reglas que nos permitan concluir que
V
definitivamente no está asignado antes del bloque que es el cuerpo de cualquier constructor, método, inicializador de instancia o inicializador estático declarado enC
Podemos concluir informalmente queV
no está definitivamente sin asignar antes del bloque que es el cuerpo de cualquier constructor, método, inicializador de instancia o inicializador estático declarado en C, pero no es necesario que dicha regla se establezca explícitamente.
En el JLS, §8.3.3. Reenviar referencias durante la inicialización de campo , se indica que hay un error en tiempo de compilación cuando:
El uso de variables de instancia cuyas declaraciones aparecen textualmente después del uso a veces está restringido, a pesar de que estas variables de instancia están dentro del alcance. Específicamente, es un error en tiempo de compilación si se cumple todo lo siguiente:
La declaración de una variable de instancia en una clase o interfaz C aparece textualmente después de un uso de la variable de instancia;
El uso es un nombre simple en un inicializador de variable de instancia de C o un inicializador de instancia 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.
Las siguientes reglas vienen con algunos ejemplos, de los cuales el más cercano al suyo es este:
class Z {
static int peek() { return j; }
static int i = peek();
static int j = 1;
}
class Test {
public static void main(String[] args) {
System.out.println(Z.i);
}
}
Los accesos [a variables estáticas o de instancia] por
métodos no se verifican de esta manera
, por lo que el código anterior produce la salida
0
, porque el inicializador de variables para
i
usa el método de clase
peek()
para acceder al valor de la variable
j
antes de que
j
inicializado por su inicializador variable, en cuyo punto todavía tiene su valor predeterminado (
§4.12.5 Valores iniciales de variables
).
Entonces, para resumir, su segundo ejemplo se compila y ejecuta bien, porque el compilador no verifica si la variable
x
ya se inicializó cuando invoca
printX()
y cuando
printX()
realmente tiene lugar en tiempo de ejecución, la variable
x
se asignará con su valor predeterminado (
0
).
La diferencia es que, en el primer caso, está llamando a
System.out.println
desde el
bloque inicializador, de
modo que el bloque se invoca antes del constructor.
En la primera linea
System.out.println(x);
La variable
x
aún no se ha inicializado, por lo que obtiene un error de compilación.
Pero en el segundo caso, llama al método de instancia que no sabe si la variable ya se ha inicializado, por lo que no tiene un error de compilación y puede ver el valor predeterminado para
x
Ok, aquí están mis 2 centavos.
Todos sabemos que las variables finales solo pueden inicializarse mientras se declara o más adelante en los constructores. Teniendo ese hecho en mente, veamos qué sucedió aquí hasta ahora.
Sin errores Caso:
Entonces, cuando se usa dentro de un método, ya tiene un valor.
1) If you initialize it, that value.
2) If not, the default value of data type.
Caso de error:
Cuando haces eso en un bloque de inicialización, que estás viendo errores.
Si nos fijamos en los
docs of initialization block
{
// whatever code is needed for initialization goes here
}
y
El compilador de Java copia bloques de inicializador en cada constructor. Por lo tanto, este enfoque se puede usar para compartir un bloque de código entre múltiples constructores.
A los ojos del compilador, su código es literalmente igual a
class HelloWorld {
final int x;
HelloWorld() {
System.out.println(x); ------------ ERROR here obviously
x = 7;
System.out.println(x);
System.out.println("hi");
}
public static void main(String[] args) {
HelloWorld t = new HelloWorld();
}
}
Lo está utilizando incluso antes de inicializarlo.
Tratamos aquí con el bloque inicializador. El compilador de Java copia bloques de inicializador en cada constructor.
El error del compilador no ocurre en el segundo ejemplo, ya que la impresión x está en otro Marco, consulte las especificaciones.
Caso 1 :
Te da un error de compilación,
Porque en
System.out.println(x);
está intentando imprimir x que nunca se inicializó.
Caso 2:
Funciona porque no está utilizando directamente ningún valor literal, sino que está llamando a algún método, que es correcto.
La regla general es,
Si está intentando acceder a cualquier variable que nunca se inicializa, le dará un error de compilación.