¿Las variables finales de Java tendrán valores predeterminados?

81

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 intento ejecutarlo, variable x might not have been initializedobtengo un error del compilador como: basado en los valores predeterminados de Java, ¿debería obtener el siguiente resultado correcto?

"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?

usuario3766874
fuente

Respuestas:

62

http://docs.oracle.com/javase/tutorial/java/javaOO/initial.html , capítulo "Inicialización de miembros de instancia":

El compilador de Java copia los bloques inicializadores 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 (de 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 que se indique 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, para que tenga un valor predecible si léalo 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 mediante 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");
}
sp00m
fuente
1
Podría valer la pena señalar dónde va la llamada implícita (o explícita) a super () en uno de sus ejemplos.
Patrick
2
Esto no responde por qué no inicializar el campo final provoca un error de compilación.
justo la mitad del
@ sp00m Buena referencia. Lo pondré en el banco.
Bohemio
2
@justhalf la respuesta falta un punto crucial. Puede acceder a un final en su estado predeterminado (a través de un método), pero el compilador se quejará si no lo inicializa antes del final del proceso de construcción. Es por eso que el segundo intento funciona (en realidad inicializa x), pero no el primero. El compilador también se quejará si intenta acceder directamente a un final en blanco.
Luca
28

JLS está diciendo que usted debe asignar el valor por defecto a la variable de instancia en blanco en el constructor (o en el bloque de inicialización que es bastante lo mismo). Por eso aparece el error en el primer caso. Sin embargo, no dice que no pueda acceder a él en el constructor antes. Parece un poco extraño, pero puede acceder a él antes de la asignación y ver el valor predeterminado para int - 0.

UPD. Como lo menciona @ I4mpi, JLS define 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 lo que respecta 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 xse asigna definitivamente al comienzo del constructor, porque contiene la asignación al final.

udalmik
fuente
En realidad, se hace decir que no se puede acceder a él antes de la asignación : "Cada variable local (§14.4) y cada final de campo en blanco (§4.12.4, §8.3.1.2) debe tener un valor asignado definitivamente cuando se produce cualquier acceso de su valor "
l4mpi
1
debería ser "definitivamente asignado", sin embargo, esta regla tiene un comportamiento extraño en términos de constructor, he actualizado la respuesta
udalmik
Si hay un método de código que, dependiendo de algunas condiciones complejas, puede o no leer un finalcampo, y si ese código puede ejecutarse tanto antes como después de que se escriba el campo, un compilador en el caso general no tendría forma de sabiendo si alguna vez leería el campo antes de que fuera escrito.
supercat
7

Si no inicializa x, obtendrá un error en tiempo de compilación ya xque nunca se inicializa.

Declarar xcomo final significa que se puede inicializar solo en el constructor o en el bloque inicializador (ya que el compilador copiará este bloque en cada constructor).

La razón por la que se 0imprime 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 que se declaran pero no se inicializan se establecerán en un valor predeterminado razonable por el compilador. En términos generales, este valor predeterminado será cero o nulo, según el tipo de datos. Sin embargo, confiar en tales valores predeterminados generalmente se considera un mal estilo de programación.

El siguiente gráfico 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
Nir Alfasi
fuente
4

El primer error es que el compilador se queja de que tiene un campo final, pero no hay código para inicializarlo, bastante simple.

En el segundo ejemplo, tiene código para asignarle un valor, pero la secuencia de ejecución significa que hace referencia al campo tanto antes como después de asignarlo.

El valor preasignado de cualquier campo es el valor predeterminado.

Bohemio
fuente
2

Todos los campos no finales de una clase se inicializan con un valor predeterminado ( 0para tipos de datos numéricos, falsepara tipos booleanos y nullde referencia, a veces llamados objetos complejos). Estos campos se inicializan antes de que un constructor (o bloque de inicialización de instancia) se ejecute independientemente de si los campos se declararon antes o después del constructor.

Los campos finales de una clase no tienen un valor predeterminado y deben inicializarse explícitamente solo una vez antes de que un constructor de clases haya terminado su trabajo.

Las variables locales en el interior de un bloque de ejecución (por ejemplo, un método) no tienen un 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.

Martin Andersson
fuente
1

Déjame expresarlo con las palabras más simples que pueda.

finallas variables deben inicializarse, esto es obligatorio por la Especificación de idioma. Dicho esto, tenga en cuenta que no es necesario inicializarlo en el momento de la declaración.

Es necesario inicializar eso antes de que se inicialice el objeto.

Podemos usar bloques inicializadores para inicializar las variables finales. Ahora, los bloques inicializadores son de dos tipos staticynon-static

El bloque que usó es un bloque inicializador no estático. Entonces, cuando crea un objeto, Runtime invocará al constructor y el cual, a su vez, invocará al constructor de la clase principal.

Después de eso, invocará a todos los inicializadores (en su caso, el inicializador no estático).

En su pregunta, caso 1 : incluso después de completar el bloque inicializador, la variable final permanece sin inicializar, lo que es un error que el compilador detectará.

En el caso 2 : el inicializador inicializará la variable final, por lo que el compilador sabe que antes de que se inicialice el objeto, la final ya está inicializada. Por tanto, no se quejará.

Ahora la pregunta es, ¿por qué xtoma un cero? La razón aquí es que el compilador ya sabe que no hay 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. Vea la invocación de inicio a continuación:

ingrese la descripción de la imagen aquí

Dharam
fuente
1

Hasta donde yo sé, el compilador siempre inicializará las variables de clase a los valores predeterminados (incluso las variables finales). Por ejemplo, si inicializara un int a 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
Michael D.
fuente
1
La variable final x no es estática en el código del OP.
JamesB
Podría modificar fácilmente el código del OP para inicializar a this.xy sucedería lo mismo. No importa si es estático o no.
Michael D.
Le sugiero que elimine el contenido estático aquí, ya que parece que no ha leído la pregunta del OP.
JamesB
¿Ayuda si me baso en el código del OP? Como dije, no importa si la variable es estática o no. Mi punto es que inicializar una variable a sí misma y obtener el valor predeterminado implica que la variable se inicializa implícitamente antes de inicializarse explícitamente.
Michael D.
1
no se compila, porque está intentando acceder (directamente) a una variable final antes de que se inicialice, en la línea 6.
Luca
1

Si trato de ejecutarlo, obtengo un error del compilador como: es posible que la variable x no se haya inicializado en función de los valores predeterminados de Java. ¿Debería obtener el siguiente resultado correcto?

"Aquí x es 0".

No. No está viendo ese resultado porque, en primer lugar, está obteniendo un error en tiempo de compilación. Las variables finales obtienen un valor predeterminado, pero la Especificación del lenguaje Java (JLS) requiere que las inicialice al final del constructor (LE: estoy incluyendo bloques de inicialización aquí); de lo contrario, obtendrá 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, intente familiarizarse con JLS. No hay mejor fuente de información sobre el lenguaje Java.

asincrónico
fuente