java d'istanza - Perché tentare di stampare una variabile non inizializzata non genera sempre un messaggio di errore





variabili classe (6)


Nel JLS, §8.3.3. Inoltra riferimenti durante l'inizializzazione del campo , ha dichiarato che c'è un errore in fase di compilazione quando:

L'uso di variabili di istanza le cui dichiarazioni appaiono testuali dopo l'uso è talvolta limitato, anche se queste variabili di istanza sono nell'ambito. In particolare, si tratta di un errore in fase di compilazione se sono vere tutte le seguenti condizioni:

  • La dichiarazione di una variabile di istanza in una classe o interfaccia C appare testualmente dopo l'uso della variabile di istanza;

  • L'uso è un nome semplice in un inizializzatore di variabile di istanza di C o un inizializzatore di istanza di C;

  • L'uso non è sul lato sinistro di un compito;

  • C è la classe più interna o l'interfaccia che racchiude l'uso.

Le seguenti regole vengono fornite con alcuni esempi, tra i quali il più vicino al tuo è questo:

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);
    }
}

Gli accessi [alle variabili statiche o di istanza] dai metodi non vengono controllati in questo modo , quindi il codice sopra produce l'output 0 , perché l'inizializzatore della variabile per usa il metodo di classe peek() per accedere al valore della variabile j prima che j sia stato inizializzato dal suo inizializzatore variabile, a quel punto ha ancora il suo valore predefinito ( §4.12.5 Valori iniziali delle variabili ).

Quindi, per riassumere, il tuo secondo esempio viene compilato ed eseguito correttamente, perché il compilatore non controlla se la variabile x stata già inizializzata quando invochi printX() e quando printX() avviene effettivamente in Runtime, la variabile x verrà assegnata con il suo valore predefinito ( 0 ).

Alcuni potrebbero trovarlo simile alla domanda SO Le variabili finali di Java avranno valori predefiniti? ma questa risposta non risolve completamente questo problema, poiché quella domanda non stampa direttamente il valore di x all'interno del blocco di inizializzazione dell'istanza.

Il problema sorge quando provo a stampare x direttamente all'interno del blocco di inizializzazione dell'istanza, pur avendo assegnato un valore a x prima della fine del blocco:

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();
    }
}

Questo dà un errore di compilazione che indica che la variabile x potrebbe non essere stata inizializzata.

$ javac HelloWorld.java
HelloWorld.java:6: error: variable x might not have been initialized
        System.out.println(x);
                           ^
1 error

Caso 2

Invece di stampare direttamente, sto chiamando una funzione per stampare:

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();
    }
}

Questo compila correttamente e dà l'output

0
7
hi

Qual è la differenza concettuale tra i due casi?




Caso 1 :

Ti dà un errore di compilazione,

Perché su System.out.println(x);

stai provando a stampare x che non è mai stato inizializzato.

Caso 2:

Funziona perché non stai usando direttamente alcun valore letterale, ma stai chiamando un metodo, che è corretto.

La regola generale è,

Se si sta tentando di accedere a qualsiasi variabile che non è mai inizializzata, verrà generato un errore di compilazione.




Leggendo il JLS, la risposta sembra essere nella sezione 16.2.2 :

Un campo membro vuoto vuoto V è definitivamente assegnato (e inoltre non è definitivamente non assegnato) prima del blocco (§14.2) che è il corpo di qualsiasi metodo nell'ambito di V e prima della dichiarazione di qualsiasi classe dichiarata nell'ambito di V

Ciò significa che quando viene chiamato un metodo, il campo finale viene assegnato al suo valore predefinito 0 prima di richiamarlo, quindi quando lo si fa riferimento all'interno del metodo, esso viene compilato correttamente e viene stampato il valore 0.

Tuttavia, quando si accede al campo al di fuori di un metodo, viene considerato non assegnato, quindi l'errore di compilazione. Anche il seguente codice non verrà compilato:

public class Main {
    final int x;
    {
        method();
        System.out.println(x);
        x = 7;
    }
    void method() { }
    public static void main(String[] args) { }
}

perché:

  • V è [un] assegnato prima di ogni altra istruzione S del blocco iff V è [un] assegnato dopo l'istruzione immediatamente precedente a S nel blocco.

Poiché il campo finale x non è assegnato prima dell'invocazione del metodo, non è ancora assegnato dopo.

Questa nota nel JLS è anche rilevante:

Nota che non ci sono regole che ci permettano di concludere che V è definitivamente non assegnato prima del blocco che è il corpo di qualsiasi costruttore, metodo, inizializzatore di istanze o inizializzatore statico dichiarato in C Possiamo concludere informalmente che V non è definitivamente non assegnato prima del blocco che è il corpo di qualsiasi costruttore, metodo, inizializzatore di istanze o inizializzatore statico dichiarato in C, ma non è necessario che tale regola venga dichiarata esplicitamente.




Ok, ecco i miei 2 centesimi.

Sappiamo tutti che le variabili finali possono essere inizializzate solo durante la dichiarazione o successivamente nei costruttori. Tenendo presente questo fatto, vediamo cosa è successo qui finora.

Nessun errore Caso:

Quindi quando usi un metodo, ha già un valore.

 1) If you initialize it, that value.
 2) If not, the default value of data type. 

Errore:

Quando lo fai in un blocco di inizializzazione, che stai vedendo errori.

Se guardi i docs of initialization block

{
    // whatever code is needed for initialization goes here
}

e

Il compilatore Java copia i blocchi di inizializzazione in ogni costruttore. Pertanto, questo approccio può essere utilizzato per condividere un blocco di codice tra più costruttori.

All'occhio del compilatore, il tuo codice è letteralmente uguale 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 stai usando prima ancora di inizializzarlo.




Trattiamo qui con il blocco di inizializzazione. Il compilatore Java copia i blocchi di inizializzazione in ogni costruttore.

L'errore del compilatore non si verifica nel secondo esempio, poiché la stampa di x è in un altro frame, fare riferimento alle specifiche.




Supponi la codifica UTF8 nel file - in caso contrario, lascia fuori l'argomento "UTF8" e utilizzerai il set di caratteri predefinito per il sistema operativo sottostante in ciascun caso.

Modo rapido in JSE 6 - Libreria semplice e senza terze parti!

import java.io.File;
public class FooTest {
  @Test public void readXMLToString() throws Exception {
        java.net.URL url = MyClass.class.getResource("test/resources/abc.xml");
        //Z means: "The end of the input but for the final terminator, if any"
        String xml = new java.util.Scanner(new File(url.toURI()),"UTF8").useDelimiter("\\Z").next();
  }
}

Modo rapido in JSE 7 (il futuro)

public class FooTest {
  @Test public void readXMLToString() throws Exception {
        java.net.URL url = MyClass.class.getResource("test/resources/abc.xml");
        java.nio.file.Path resPath = java.nio.file.Paths.get(url.toURI());
        String xml = new String(java.nio.file.Files.readAllBytes(resPath), "UTF8"); 
  }

Né inteso per file enormi però.







java initialization final