tl; dr
Per i campi , int b = b + 1
è illegale perché b
è un riferimento in avanti illegale a b
. Puoi effettivamente risolvere questo problema scrivendo int b = this.b + 1
, che viene compilato senza lamentele.
Per le variabili locali , int d = d + 1
è illegale perché d
non viene inizializzato prima dell'uso. Questo non è il caso dei campi, che sono sempre inizializzati per impostazione predefinita.
Puoi vedere la differenza tentando di compilare
int x = (x = 1) + x;
come dichiarazione di campo e come dichiarazione di variabile locale. Il primo fallirà, ma il secondo avrà successo, a causa della differenza di semantica.
introduzione
Prima di tutto, le regole per gli inizializzatori di campo e di variabili locali sono molto diverse. Quindi questa risposta affronterà le regole in due parti.
Useremo questo programma di test in tutto:
public class test {
int a = a = 1;
int b = b + 1;
public static void Main(String[] args) {
int c = c = 1;
int d = d + 1;
}
}
La dichiarazione di b
non è valida e ha esito negativo con un illegal forward reference
errore.
La dichiarazione di d
non è valida e ha esito negativo con un variable d might not have been initialized
errore.
Il fatto che questi errori siano diversi dovrebbe suggerire che anche i motivi degli errori sono diversi.
Campi
Gli inizializzatori di campo in Java sono regolati da JLS §8.3.2 , Inizializzazione dei campi.
L' ambito di un campo è definito in JLS §6.3 , Scopo di una dichiarazione.
Le regole rilevanti sono:
- L'ambito di una dichiarazione di un membro
m
dichiarato o ereditato da un tipo di classe C (§8.1.6) è l'intero corpo di C, comprese le dichiarazioni di tipo annidate.
- Le espressioni di inizializzazione per le variabili di istanza possono utilizzare il semplice nome di qualsiasi variabile statica dichiarata o ereditata dalla classe, anche una la cui dichiarazione si verifica successivamente in modo testuale.
- L'uso di variabili di istanza le cui dichiarazioni appaiono in modo testuale dopo l'uso è talvolta limitato, anche se queste variabili di istanza rientrano nell'ambito. Vedere §8.3.2.3 per le regole precise che governano il riferimento diretto alle variabili di istanza.
§8.3.2.3 dice:
La dichiarazione di un membro deve apparire testualmente prima di essere utilizzata solo se il membro è un campo di istanza (rispettivamente statico) di una classe o di un'interfaccia C e tutte le seguenti condizioni sono valide:
- L'utilizzo si verifica in un inizializzatore di variabile di istanza (rispettivamente statica) di C o in un inizializzatore di istanza (rispettivamente statica) di C.
- L'utilizzo non si trova sul lato sinistro di un compito.
- L'utilizzo avviene tramite un semplice nome.
- C è la classe o l'interfaccia più interna che racchiude l'utilizzo.
Puoi effettivamente fare riferimento ai campi prima che siano stati dichiarati, tranne in alcuni casi. Queste restrizioni hanno lo scopo di impedire codice simile
int j = i;
int i = j;
dalla compilazione. La specifica Java dice che "le limitazioni di cui sopra sono progettate per catturare, in fase di compilazione, inizializzazioni circolari o altrimenti non corrette".
A cosa si riducono effettivamente queste regole?
In breve, le regole in sostanza dicono che devi dichiarare un campo prima di un riferimento a quel campo se (a) il riferimento è in un inizializzatore, (b) il riferimento non viene assegnato, (c) il riferimento è un nome semplice (nessun qualificatore come this.
) e (d) non vi si accede dall'interno di una classe interna. Quindi, un riferimento in avanti che soddisfa tutte e quattro le condizioni è illegale, ma un riferimento in avanti che non riesce in almeno una condizione è OK.
int a = a = 1;
compila perché viola (b): il riferimento a
è stato assegnato, quindi è legale fare riferimento a
prima della a
dichiarazione completa di.
int b = this.b + 1
compila anche perché viola (c): il riferimento this.b
non è un nome semplice (è qualificato con this.
). Questo strano costrutto è ancora perfettamente ben definito, perché this.b
ha valore zero.
Quindi, fondamentalmente, le restrizioni sui riferimenti ai campi all'interno degli inizializzatori impediscono int a = a + 1
di essere compilati correttamente.
Si noti che la dichiarazione del campo nonint b = (b = 1) + b
verrà compilata, perché il finale è ancora un riferimento in avanti illegale.b
Variabili locali
Le dichiarazioni di variabili locali sono regolate da JLS §14.4 , Dichiarazioni di dichiarazione di variabili locali.
L' ambito di una variabile locale è definito in JLS §6.3 , Scopo di una dichiarazione:
- Lo scopo di una dichiarazione di variabile locale in un blocco (§14.4) è il resto del blocco in cui appare la dichiarazione, a partire dal proprio inizializzatore e includendo eventuali ulteriori dichiaratori a destra nell'istruzione di dichiarazione della variabile locale.
Notare che gli inizializzatori rientrano nell'ambito della variabile dichiarata. Allora perché non int d = d + 1;
compila?
Il motivo è dovuto alla regola di Java sull'assegnazione definitiva ( JLS §16 ). L'assegnazione definita fondamentalmente dice che ogni accesso a una variabile locale deve avere un precedente assegnamento a quella variabile, e il compilatore Java controlla loop e rami per garantire che l'assegnazione avvenga sempre prima di qualsiasi utilizzo (questo è il motivo per cui l'assegnazione definita ha un'intera sezione di specifica dedicata ad esso). La regola di base è:
- Ad ogni accesso di una variabile locale o di un campo finale vuoto
x
, x
deve essere assegnato definitivamente prima dell'accesso, altrimenti si verifica un errore in fase di compilazione.
In int d = d + 1;
, l'accesso a d
viene risolto alla variabile locale fine, ma poiché d
non è stato assegnato prima dell'accesso d
, il compilatore emette un errore. In int c = c = 1
, c = 1
accade prima, che assegna c
e quindi c
viene inizializzato sul risultato di quell'assegnazione (che è 1).
Si noti che a causa di regole di assegnazione definite, la dichiarazione della variabile locale int d = (d = 1) + d;
verrà compilata correttamente (a differenza della dichiarazione del campo int b = (b = 1) + b
), perché d
viene assegnata definitivamente quando d
viene raggiunta la finale .
static
nella variabile dell'ambito della classe, come instatic int x = x + 1;
, otterrai lo stesso errore? Perché in C # fa la differenza se è statico o non statico.