Una scoperta molto interessante. Per capirlo dobbiamo scavare nella Java Language Specification ( JLS ).
Il motivo è che finalconsente solo un compito . Il valore predefinito, tuttavia, non è un'assegnazione . In effetti, ciascuna di tali variabili ( variabile di classe, variabile di istanza, componente di matrice) punta al suo valore predefinito dall'inizio, prima delle assegnazioni . Il primo compito quindi modifica il riferimento.
Variabili di classe e valore predefinito
Dai un'occhiata al seguente esempio:
private static Object x;
public static void main(String[] args) {
System.out.println(x); // Prints 'null'
}
Non abbiamo assegnato esplicitamente un valore a x, sebbene punti a null, è il valore predefinito. Confrontalo con §4.12.5 :
Valori iniziali delle variabili
Ogni variabile di classe , variabile di istanza o componente dell'array viene inizializzata con un valore predefinito quando viene creata ( §15.9 , §15.10.2 )
Nota che questo vale solo per quel tipo di variabili, come nel nostro esempio. Non vale per le variabili locali, vedere il seguente esempio:
public static void main(String[] args) {
Object x;
System.out.println(x);
// Compile-time error:
// variable x might not have been initialized
}
Dallo stesso paragrafo JLS:
Una variabile locale ( §14.4 , §14.14 ) deve ricevere esplicitamente un valore prima di essere utilizzata, mediante inizializzazione ( §14.4 ) o assegnazione ( §15.26 ), in un modo che può essere verificato utilizzando le regole per l'assegnazione definitiva ( § 16 (Assegnazione definita) ).
Variabili finali
Ora diamo un'occhiata final, dal §4.12.4 :
Variabili finali
Una variabile può essere dichiarata finale . Un ultimo variabile può essere solo assegnato a volta . È un errore in fase di compilazione se viene assegnata una variabile finale a meno che non sia definitivamente non assegnata immediatamente prima dell'assegnazione ( §16 (Assegnazione definita) ).
Spiegazione
Ora torniamo al tuo esempio, leggermente modificato:
public static void main(String[] args) {
System.out.println("After: " + X);
}
private static final long X = assign();
private static long assign() {
// Access the value before first assignment
System.out.println("Before: " + X);
return X + 1;
}
Emette
Before: 0
After: 1
Ricorda ciò che abbiamo imparato. All'interno del metodo assignalla variabile non èX stato ancora assegnato un valore. Pertanto, punta al suo valore predefinito poiché è una variabile di classe e secondo la JLS tali variabili puntano sempre immediatamente ai loro valori predefiniti (contrariamente alle variabili locali). Dopo il assignmetodo alla variabile Xviene assegnato il valore 1e per finalquesto non possiamo più cambiarlo. Quindi il seguente non funzionerebbe a causa di final:
private static long assign() {
// Assign X
X = 1;
// Second assign after method will crash
return X + 1;
}
Esempio nel JLS
Grazie a @Andrew ho trovato un paragrafo JLS che copre esattamente questo scenario, lo dimostra anche.
Ma prima diamo un'occhiata
private static final long X = X + 1;
// Compile-time error:
// self-reference in initializer
Perché questo non è consentito, mentre l'accesso dal metodo è? Dai un'occhiata al § 8.3.3 che parla di quando gli accessi ai campi sono limitati se il campo non è stato ancora inizializzato.
Elenca alcune regole rilevanti per le variabili di classe:
Per un riferimento in base al nome semplice a una variabile di classe fdichiarata in classe o interfaccia C, è un errore di compilazione se :
Il riferimento appare in un inizializzatore variabile di classe di Co in un inizializzatore statico di C( §8.7 ); e
Il riferimento appare o nell'inizializzatore del fproprio dichiaratore o in un punto alla sinistra del fdichiaratore; e
Il riferimento non si trova sul lato sinistro di un'espressione di assegnazione ( §15.26 ); e
La classe o l'interfaccia più interna che racchiude il riferimento è C.
È semplice, X = X + 1viene catturato da queste regole, l'accesso al metodo no. Elencano persino questo scenario e danno un esempio:
Gli accessi con i metodi non vengono controllati in questo modo, quindi:
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);
}
}
produce l'output:
0
poiché l'inizializzatore della variabile iutilizza il metodo di classe peek per accedere al valore della variabile jprima che jsia stato inizializzato dal suo inizializzatore di variabile, a quel punto ha ancora il suo valore predefinito ( §4.12.5 ).
Xmembro è come fare riferimento a un membro della sottoclasse prima che il costruttore della superclasse abbia terminato, questo è il tuo problema e non la definizione difinal.