Uso del campo finale non inizializzato – con / senza “questo”. qualificatore

Qualcuno può spiegarmi perché compila il primo dei due seguenti esempi, mentre il secondo no? Notare che l’unica differenza è che il primo qualifica esplicitamente il riferimento a x con “.questo”, mentre il secondo no. In entrambi i casi, il campo finale x è chiaramente tentato di essere utilizzato prima dell’inizializzazione.

Avrei pensato che entrambi i campioni sarebbero stati trattati in modo completamente uguale, risultando in un errore di compilazione per entrambi.

1)

public class Foo { private final int x; private Foo() { int y = 2 * this.x; x = 5; } } 

2)

 public class Foo { private final int x; private Foo() { int y = 2 * x; x = 5; } } 

Dopo un po ‘di lettura e riflessione delle speculazioni, ho concluso che:

In un compilatore Java 5 o Java 6, questo è un comportamento corretto. Capitolo 16 “Assegnazione definitiva di Java Language Specification , Third Edition dice:

Ogni variabile locale (§14.4) e ogni campo vuoto final (§4.12.4) (§8.3.1.2) devono avere un valore assegnato in modo definitivo quando si verifica un qualsiasi accesso al suo valore. Un accesso al suo valore consiste nel semplice nome della variabile che si trova ovunque in un’espressione tranne come l’operando di sinistra dell’operatore di assegnazione semplice = .

(sottolineatura mia). Quindi nell’espressione 2 * this.x , la parte this.x non è considerata un “accesso del valore di [ x ‘s]” (e quindi non è soggetta alle regole di assegnazione definita), perché this.x non è il nome semplice della variabile di istanza x . (NB la regola per quando si verifica un assegnamento definito, nel paragrafo dopo il testo sopra citato, ammette qualcosa come this.x = 3 , e considera x definitivamente assegnata in seguito, è solo la regola per gli accessi che non conta this.x ) Si noti che il valore di this.x in questo caso sarà zero, per §17.5.2 .

In un compilatore Java 7, questo è un bug del compilatore, ma comprensibile. Capitolo 16 “Definite Assignment” della specifica del linguaggio Java, Java 7 SE Edition dice:

Ogni variabile locale ( §14.4 ) e ogni campo final vuoto ( §4.12.4 , §8.3.1.2 ) devono avere un valore assegnato in modo definitivo quando si verifica un qualsiasi accesso al suo valore.

Un accesso al suo valore consiste nel semplice nome della variabile (o, per un campo, il semplice nome del campo qualificato da this ) che si trova ovunque in un’espressione tranne come l’operando di sinistra dell’operatore di assegnazione semplice = ( § 15.26.1 ).

(sottolineatura mia). Quindi nell’espressione 2 * this.x , la parte this.x dovrebbe essere considerata un “accesso al valore di [ x ‘s]” e dovrebbe dare un errore di compilazione.

Ma non hai chiesto se il primo dovesse essere compilato, hai chiesto perché lo compila (in alcuni compilatori). Questo è necessariamente speculativo, ma farò due ipotesi:

  1. La maggior parte dei compilatori Java 7 sono stati scritti modificando i compilatori Java 6. Alcuni compilatori potrebbero non aver notato questa modifica. Inoltre, molti compilatori e IDE Java-7 supportano ancora Java 6 e alcuni compilatori potrebbero non sentirsi motivati ​​a rifiutare in modo specifico qualcosa in modalità Java-7 che accettano in modalità Java-6.
  2. Il nuovo comportamento di Java 7 è stranamente incoerente. Qualcosa di simile (false ? null : this).x è ancora permesso, e del (this).x , anche (this).x è ancora permesso; è solo la sequenza di token specifica this plus . più il nome del campo che è interessato da questa modifica. Certo, tale incoerenza esisteva già sul lato sinistro di un’istruzione di assegnazione (possiamo scrivere this.x = 3 , ma non (this).x = 3 ), ma questo è più facilmente comprensibile: accetta this.x = 3 come caso speciale consentito della costruzione altrimenti vietata obj.x = 3 . Ha senso permetterlo. Ma non credo che abbia senso rifiutare 2 * this.x come un caso speciale proibito della costruzione altrimenti consentita 2 * obj.x , dato che (1) questo caso proibito speciale è facilmente aggirato aggiungendo parentesi, che (2) questo caso proibito speciale era permesso nelle versioni precedenti della lingua e che (3) abbiamo ancora bisogno della regola speciale in base final quale i campi final hanno i loro valori predefiniti (es. 0 per un int ) finché non sono inizializzati, sia per casi come (this).x , e per casi come this.foo() dove foo() è un metodo che accede a x . Quindi alcuni compilatori potrebbero non sentirsi motivati ​​a fare questo cambiamento incoerente.

Ognuno di questi sarebbe sorprendente – presumo che i compilatori-scrittori avessero informazioni dettagliate su ogni singola modifica alle specifiche, e nella mia esperienza i compilatori Java sono generalmente abbastanza bravi a rispettare esattamente le specifiche (a differenza di alcuni linguaggi, dove ogni compilatore ha il suo proprio dialetto) – ma, beh, è ​​successo qualcosa , e quanto sopra sono le mie uniche due ipotesi.

Quando lo si utilizza nel costruttore, il compilatore sta vedendo x come attributo membro di this object (inizializzato di default) . Poiché x è int , è predefinito inizializzato con 0 . Ciò rende il compilatore felice e funziona correttamente anche in fase di esecuzione.

Quando non lo usi, il compilatore usa la dichiarazione x direttamente nell’analisi lessicale e quindi si lamenta della sua inizializzazione ( fenomeno del tempo di compilazione ).

Quindi è la definizione di this , che rende il compilatore di analizzare x come variabile membro di un object rispetto all’attributo diretto durante l’analisi lessicale nella compilazione e risultante in un comportamento di compilazione diverso.

Quando viene utilizzata come espressione primaria, la parola chiave indica un valore che è un riferimento all’object per il quale è stato richiamato il metodo di istanza (§15.12) o all’object che si sta costruendo.

Penso che il compilatore stima che scrivere this.x implichi ‘questo’ esiste, quindi è stato chiamato un costruttore (e la variabile finale è stata inizializzata). Ma dovresti ottenere una RuntimeException quando provi ad eseguirla

Presumo che tu faccia riferimento al comportamento in Eclipse. (Come dichiarato come commento, una compilazione con javac funziona).

Penso che questo sia un problema di Eclipse. Ha un proprio compilatore e una propria serie di regole. Uno di questi è che non si può accedere ad un campo che non è inizializzato, sebbene il Java-commpiler inizializzi le variabili per te.