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


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, obtengo un error de compilación como: variable x might not have been initialized en función de los valores predeterminados de Java, debería obtener la siguiente salida ¿verdad?

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


74
2017-07-28 07:49


origen


Respuestas:


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 ha sido definitivamente asignado, 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 a   el final de cada constructor de la clase en la que se encuentra   declarado; 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 lo lees 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");
}

57
2017-07-28 08:03



JLS es diciendo que Tú debe asignar el valor predeterminado a la variable de instancia final en blanco en el constructor (o en bloque de inicialización que es bastante similar). 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 lo menciona @ I4mpi, JLS  define la regla de que cada valor debe ser definitivamente asignado 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 un 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 es definitivamente asignado al comienzo del constructor, porque contiene la asignación al final del mismo.


27
2017-07-28 08:08



Si no se inicializa x obtendrá un error en tiempo de compilación desde x nunca se inicializa

Declarando x como final significa que solo se puede inicializar en el constructor o en bloque inicializador (dado que este bloque será copiado por el compilador en cada constructor).

La razón por la que obtienes 0 impreso antes de que la variable se inicializa se debe al comportamiento definido en el manual(ver: 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   predeterminado razonable por el compilador. En términos generales, este valor predeterminado   será cero o nulo, según el tipo de datos. Confiando en tal   los valores predeterminados, sin embargo, generalmente se consideran mala programación   estilo.

El siguiente cuadro resume los valores predeterminados para los datos anteriores   tipos.

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

7
2017-07-28 08:06



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.


4
2017-07-28 08:19



Todos los campos no finales de una clase se inicializan a un valor predeterminado (0 para tipos de datos numéricos, false para booleano y null para tipos de referencia, a veces llamados 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.

Final campos de una clase tiene sin valor predeterminado y debe 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.


2
2017-07-28 08:06



Déjame ponerlo en las palabras más simples que pueda.

final las variables 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 tu 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 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 se puede cambiar con una declaración de asignación real similar a x=7. Ver la invocación de inicio a continuación:

enter image description here


1
2017-07-28 08:13



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

1
2017-07-28 08:05



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.


1
2017-07-28 22:09