una - ¿Las variables finales de Java tendrán valores predeterminados?
que es una variable static java (8)
Si trato de ejecutarlo, estoy obteniendo el error de compilación como: la variable x podría no haberse inicializado en base a los valores predeterminados de java, ¿debería obtener la siguiente salida correcta?
"Aquí x es 0".
No. No está viendo esa salida porque está obteniendo un error en tiempo de compilación en primer lugar. Las variables finales obtienen un valor predeterminado, pero la Especificación del lenguaje Java (JLS) requiere que las inicialices al final del constructor (LE: aquí incluyo los bloques de inicialización), de lo contrario obtendrás un error en tiempo de compilación que evitará que su código sea compilado y ejecutado.
Su segundo ejemplo respeta el requisito, por eso (1) su código se compila y (2) obtiene el comportamiento esperado.
En el futuro, intenta familiarizarte con el JLS. No hay mejor fuente de información sobre el lenguaje Java.
Tengo un programa como este:
class Test {
final int x;
{
printX();
}
Test() {
System.out.println("const called");
}
void printX() {
System.out.println("Here x is " + x);
}
public static void main(String[] args) {
Test t = new Test();
}
}
Si trato de ejecutarlo, estoy obteniendo el error de compilación como: la variable x might not have been initialized
base a los valores predeterminados de java, ¿debería obtener la siguiente salida correcta?
"Here x is 0".
¿Las variables finales tendrán valores predeterminados?
si cambio mi código así,
class Test {
final int x;
{
printX();
x = 7;
printX();
}
Test() {
System.out.println("const called");
}
void printX() {
System.out.println("Here x is " + x);
}
public static void main(String[] args) {
Test t = new Test();
}
}
Estoy obteniendo resultados como:
Here x is 0
Here x is 7
const called
¿Alguien puede explicar este comportamiento?
Déjame ponerlo en las palabras más simples que pueda.
final
variables final
deben inicializarse, esto es obligatorio por la especificación del lenguaje. Una vez dicho esto, tenga en cuenta que no es necesario inicializarlo en el momento de la declaración.
Se requiere inicializar eso antes de que el objeto se inicialice.
Podemos usar bloques de inicialización para inicializar las variables finales. Ahora, los bloques de inicialización son de dos tipos static
y non-static
El bloque que usaste es un bloque de inicializador no estático. Por lo tanto, cuando crea un objeto, Runtime invocará el constructor y, a su vez, invocará el constructor de la clase padre.
Después de eso, invocará todos los inicializadores (en su caso, el inicializador no estático).
En su pregunta, caso 1 : incluso después de completar el bloqueo del inicializador, la variable final permanece sin inicializar, que es un error que el compilador detectará.
En el caso 2 : el inicializador inicializará la variable final, por lo tanto, el compilador sabe que antes de que el objeto se inicialice, el final ya está inicializado. Por lo tanto, no se quejará.
Ahora la pregunta es, ¿por qué x
toma un cero. La razón aquí es que el compilador ya sabe que no hay ningún error y, por lo tanto, al invocar el método init, todos los finales se inicializarán a los valores predeterminados, y se establecerá un indicador que pueden cambiar en una declaración de asignación real similar a x=7
. Ver la invocación de inicio a continuación:
El primer error es que el compilador se queja de que tiene un campo final, pero no tiene código para inicializarlo, lo suficientemente simple.
En el segundo ejemplo, tiene un código para asignarle un valor, pero la secuencia de ejecución significa que hace referencia al campo antes y después de asignarlo.
El valor preasignado de cualquier campo es el valor predeterminado.
Por lo que sé, el compilador siempre inicializará las variables de clase a los valores predeterminados (incluso las variables finales). Por ejemplo, si tuviera que inicializar un int para sí mismo, el int se establecería en su valor predeterminado de 0. Vea a continuación:
class Test {
final int x;
{
printX();
x = this.x;
printX();
}
Test() {
System.out.println("const called");
}
void printX() {
System.out.println("Here x is " + x);
}
public static void main(String[] args) {
Test t = new Test();
}
}
Lo anterior imprimiría lo siguiente:
Here x is 0
Here x is 0
const called
Si no inicializa x
obtendrá un error en tiempo de compilación ya que x
nunca se inicializa.
Declarar x
como final significa que solo se puede inicializar en el constructor o en initializer-block (ya que el compilador copiará este bloque en cada constructor).
La razón por la que se imprime 0
antes de que se inicialice la variable se debe al comportamiento definido en el manual (consulte la sección "Valores predeterminados"):
Valores predeterminados
No siempre es necesario asignar un valor cuando se declara un campo. Los campos declarados pero no inicializados se establecerán en un valor razonable predeterminado por el compilador. En general, este valor predeterminado será cero o nulo, según el tipo de datos. Sin embargo, confiar en dichos valores predeterminados generalmente se considera un mal estilo de programación.
El siguiente cuadro resume los valores predeterminados para los tipos de datos anteriores.
Data Type Default Value (for fields)
--------------------------------------
byte 0
short 0
int 0
long 0L
float 0.0f
double 0.0d
char ''/u0000''
String (or any object) null
boolean false
Todos los campos no finales de una clase se inicializan a un valor predeterminado ( 0
para los tipos de datos numéricos, false
para booleano y null
para los tipos de referencia, a veces denominados objetos complejos). Estos campos se inicializan antes de que un constructor (o un bloque de inicialización de instancia) se ejecute independientemente de si los campos fueron declarados antes o después del constructor.
Los campos finales de una clase no tienen valor predeterminado y deben inicializarse explícitamente solo una vez antes de que un constructor de clase haya terminado su trabajo.
Las variables locales en el interior de un bloque de ejecución (por ejemplo, un método) no tienen valor predeterminado. Estos campos deben inicializarse explícitamente antes de su primer uso y no importa si la variable local está marcada como final o no.
http://docs.oracle.com/javase/tutorial/java/javaOO/initial.html , capítulo "Inicializando miembros de instancia":
El compilador de Java copia bloques de inicializador en cada constructor.
Es decir:
{
printX();
}
Test() {
System.out.println("const called");
}
se comporta exactamente como:
Test() {
printX();
System.out.println("const called");
}
Como puede ver, una vez que se ha creado una instancia, el campo final no se ha asignado definitivamente , mientras que (desde http://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.3.1.2 ):
Una variable de instancia final en blanco debe asignarse definitivamente al final de cada constructor de la clase en la que se declara; de lo contrario, se produce un error en tiempo de compilación.
Si bien no parece expresarse explícitamente en los documentos (al menos no he podido encontrarlo), un campo final debe tomar temporalmente su valor predeterminado antes del final del constructor, de modo que tenga un valor predecible si léelo antes de su asignación.
Valores predeterminados: http://docs.oracle.com/javase/specs/jls/se7/html/jls-4.html#jls-4.12.5
En su segundo fragmento, x se inicializa en la creación de la instancia, por lo que el compilador no se queja:
Test() {
printX();
x = 7;
printX();
System.out.println("const called");
}
También tenga en cuenta que el siguiente enfoque no funciona. El uso del valor predeterminado de la variable final solo se permite a través de un método.
Test() {
System.out.println("Here x is " + x); // Compile time error : variable ''x'' might not be initialized
x = 7;
System.out.println("Here x is " + x);
System.out.println("const called");
}
JLS está http://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.3.1.2 que debe asignar el valor predeterminado a la variable de instancia final en blanco en el constructor (o en el bloque de inicialización, que es prácticamente el mismo). Es por eso que obtienes el error en el primer caso. Sin embargo, no dice que no puedas acceder a él en el constructor antes. Se ve raro un poco, pero puede acceder antes de la asignación y ver el valor predeterminado para int - 0.
UPD. Como se menciona en @ I4mpi, JLS defines la regla de que cada valor debe asignarse definitivamente antes de cualquier acceso:
Each local variable (§14.4) and every blank final field (§4.12.4, §8.3.1.2) must have a definitely assigned value when any access of its value occurs.
Sin embargo, también tiene una regla interesante en cuanto a constructores y campos:
If C has at least one instance initializer or instance variable initializer then V is [un]assigned after an explicit or implicit superclass constructor invocation if V is [un]assigned after the rightmost instance initializer or instance variable initializer of C.
Entonces, en el segundo caso, el valor x
definitivamente se asigna al comienzo del constructor, porque contiene la asignación al final del mismo.