Perché le variabili locali non sono inizializzate in Java?

C’era qualche ragione per cui i progettisti di Java ritenevano che le variabili locali non dovessero avere un valore predefinito? Seriamente, se alle variabili di istanza può essere assegnato un valore predefinito, allora perché non possiamo fare lo stesso per le variabili locali?

E porta anche a problemi come spiegato in questo commento a un post sul blog :

Bene, questa regola è più frustrante quando si tenta di chiudere una risorsa in un blocco finale. Se istanziato la risorsa all’interno di una prova, ma provo a chiuderla entro la fine, ottengo questo errore. Se sposto l’istanziazione al di fuori del tentativo, ricevo un altro errore che indica che deve essere all’interno di una prova.

Molto frustrante.

Le variabili locali sono dichiarate principalmente per fare qualche calcolo. Quindi è la decisione del programmatore di impostare il valore della variabile e non dovrebbe assumere un valore predefinito. Se il programmatore, per errore, non ha inizializzato una variabile locale e ha assunto il valore predefinito, l’output potrebbe essere un valore inaspettato. Quindi, nel caso di variabili locali, il compilatore chiederà al programmatore di inizializzarsi con qualche valore prima di accedere alla variabile per evitare l’uso di valori non definiti.

Il “problema” a cui ti colleghi sembra descrivere questa situazione:

SomeObject so; try { // Do some work here ... so = new SomeObject(); so.DoUsefulThings(); } finally { so.CleanUp(); // Compiler error here } 

La lamencanvas del commentatore è che il compilatore si blocca sulla riga nella sezione finale, sostenendo che potrebbe essere non inizializzato. Il commento menziona poi un altro modo di scrivere il codice, probabilmente qualcosa del genere:

 // Do some work here ... SomeObject so = new SomeObject(); try { so.DoUsefulThings(); } finally { so.CleanUp(); } 

Il commentatore non è soddisfatto di questa soluzione perché il compilatore dice che il codice “deve essere all’interno di una prova”. Immagino che questo significhi che parte del codice potrebbe generare un’eccezione che non viene più gestita. Non ne sono sicuro. Nessuna versione del mio codice gestisce eccezioni, quindi qualsiasi cosa relativa alle eccezioni nella prima versione dovrebbe funzionare allo stesso modo nel secondo.

Ad ogni modo, questa seconda versione di codice è il modo corretto di scriverlo. Nella prima versione, il messaggio di errore del compilatore era corretto. La variabile so potrebbe non essere inizializzata. In particolare, se il costruttore SomeObject fallisce, so non verrà inizializzato, e quindi sarà un errore tentare di chiamare so.CleanUp . Immettere sempre la sezione di try dopo aver acquisito la risorsa che la sezione finale finalizza.

Il blocco tryfinally dopo la fase di inizializzazione è lì solo per proteggere l’istanza SomeObject , per assicurarsi che venga pulito, qualunque cosa accada. Se ci sono altre cose che devono essere eseguite, ma non sono correlate al fatto che l’istanza SomeObject fosse assegnata a proprietà, allora dovrebbero andare in un altro tryfinally block, probabilmente uno che include quello che ho mostrato.

Richiedere le variabili da assegnare manualmente prima dell’uso non porta a problemi reali. Porta solo a seccature minori, ma il tuo codice sarà migliore per questo. Avrai variabili con un ambito più limitato e try , finally blocchi che non cercano di proteggere troppo.

Se le variabili locali avevano valori predefiniti, quindi nel primo esempio sarebbe stato null . Questo non avrebbe davvero risolto nulla. Invece di ottenere un errore in fase di compilazione nel blocco finally , dovresti avere una NullPointerException agguato che potrebbe hide qualsiasi altra eccezione possa verificarsi nella sezione “Fai un po ‘di lavoro qui” del codice. (Oppure le eccezioni nelle sezioni finally ricorrono automaticamente all’eccezione precedente? Non ricordo. Anche così, avresti un’eccezione in più rispetto a quella reale.)

Inoltre, nell’esempio seguente, potrebbe essere stata generata un’eccezione all’interno della struttura SomeObject, nel qual caso la variabile “so” sarebbe nullo e la chiamata a CleanUp genererà una NullPointerException

 SomeObject so; try { // Do some work here ... so = new SomeObject(); so.DoUsefulThings(); } finally { so.CleanUp(); // Compiler error here } 

Quello che tendo a fare è questo:

 SomeObject so = null; try { // Do some work here ... so = new SomeObject(); so.DoUsefulThings(); } finally { if (so != null) { so.CleanUp(); // safe } } 

Si noti che l’ultima istanza / le variabili membro non vengono inizializzate per impostazione predefinita. Perché quelli sono finali e non possono essere modificati nel programma in seguito. Questa è la ragione per cui Java non fornisce alcun valore predefinito per loro e costringe il programmatore a inizializzarlo.

D’altra parte, le variabili dei membri non finali possono essere modificate in seguito. Quindi il compilatore non consente loro di rimanere non inizializzati, precisamente, perché possono essere modificati in seguito. Riguardo alle variabili locali, l’ambito delle variabili locali è molto più ristretto. Il compilatore sa quando si sta abituando. Quindi, forzare il programmatore a inizializzare la variabile ha senso.

La risposta effettiva alla tua domanda è perché le variabili di metodo vengono istanziate semplicemente aggiungendo un numero al puntatore dello stack. Azzerarli sarebbe un passo in più. Per le variabili di class vengono inserite nella memoria inizializzata nell’heap.

Perché non fare il passo in più? Fai un passo indietro – Nessuno ha menzionato che il “avvertimento” in questo caso è una cosa molto buona.

Non dovresti mai inizializzare la tua variabile su zero o null al primo passaggio (quando la stai codificando per la prima volta). Assegnalo al valore reale o non assegnarlo affatto perché se non lo fai allora java può dirti quando fai davvero casino. Prendi la risposta di Electric Monk come un grande esempio. Nel primo caso, è davvero incredibilmente utile che ti stia dicendo che se try () fallisce perché il costruttore di SomeObject ha lanciato un’eccezione, alla fine si finirebbe con un NPE. Se il costruttore non può lanciare un’eccezione, non dovrebbe essere nel tentativo.

Questo avviso è un fantastico correttore di programmatori di percorsi multipli che mi ha salvato dal fare cose stupide dal momento che controlla ogni percorso e fa in modo che, se si utilizza la variabile in qualche percorso, si debba inizializzarlo in ogni percorso che porta ad esso . Non ho mai inizializzato le variabili in modo esplicito fino a quando non stabilisco che è la cosa giusta da fare.

Oltre a questo, non è meglio dire esplicitamente “int size = 0” piuttosto che “int size” e fare in modo che il prossimo programmatore capisca che intendi che sia zero?

D’altra parte, non riesco a trovare un unico motivo valido per far inizializzare il compilatore tutte le variabili non inizializzate su 0.

(Può sembrare strano postare una nuova risposta così a lungo dopo la domanda, ma è apparso un duplicato .)

Per me, la ragione si riduce a questo: lo scopo delle variabili locali è diverso dallo scopo delle variabili di istanza. Le variabili locali sono lì da utilizzare come parte di un calcolo; le variabili di istanza sono lì per contenere lo stato. Se si utilizza una variabile locale senza assegnargli un valore, è quasi certamente un errore logico.

Detto questo, potrei assolutamente rimanere indietro richiedendo che le variabili di istanza siano sempre state inizializzate in modo esplicito; l’errore si verificava su qualsiasi costruttore in cui il risultato consentiva una variabile di istanza non inizializzata (ad esempio, non inizializzata nella dichiarazione e non nel costruttore). Ma questa non è la decisione Gosling, et. Al., ha preso nei primi anni ’90, quindi eccoci qui. (E non sto dicendo che hanno fatto la chiamata sbagliata.)

Non sono riuscito a recuperare le variabili locali di default, comunque. Sì, non dovremmo affidarci ai compilatori per ricontrollare la nostra logica, e uno no, ma è ancora a portata di mano quando il compilatore ne rileva uno. 🙂

Penso che lo scopo principale fosse mantenere la somiglianza con C / C ++. Tuttavia il compilatore rileva e ti avvisa sull’utilizzo di variabili non inizializzate che ridurranno il problema a un punto minimale. Dal punto di vista delle prestazioni, è un po ‘più veloce per consentire di dichiarare variabili non inizializzate poiché il compilatore non dovrà scrivere un’istruzione di assegnazione, anche se si sovrascrive il valore della variabile nella successiva istruzione.

È più efficiente non inizializzare le variabili, e nel caso delle variabili locali è sicuro farlo, perché l’inizializzazione può essere tracciata dal compilatore.

Nei casi in cui è necessario inizializzare una variabile, è sempre ansible farlo da soli, quindi non è un problema.

Eclipse ti dà persino avvertimenti su variabili non inizializzate, quindi diventa abbastanza ovvio in ogni caso. Personalmente penso che sia una buona cosa che questo sia il comportamento predefinito, altrimenti la tua applicazione potrebbe usare valori inaspettati, e invece del compilatore che lancia un errore non farà nulla (ma forse darà un avvertimento) e poi ti gratterai la tua testa sul perché certe cose non si comportano come dovrebbero.

Le variabili locali sono memorizzate su uno stack, ma le variabili di istanza sono memorizzate nell’heap, quindi ci sono alcune probabilità che un valore precedente sullo stack venga letto anziché un valore predefinito come accade nell’heap. Per questo motivo jvm non consente di utilizzare una variabile locale senza inizializzarla.

La variabile istanza avrà valori predefiniti ma le variabili locali non potrebbero avere valori predefiniti. Poiché le variabili locali sono fondamentalmente in metodi / comportamento, il suo scopo principale è fare alcune operazioni o calcoli. Pertanto, non è una buona idea impostare valori predefiniti per le variabili locali. In caso contrario, è molto difficile e richiede molto tempo per verificare le ragioni di risposte inaspettate.

La risposta è che le variabili di istanza possono essere inizializzate nel costruttore della class o in qualsiasi metodo di class, ma nel caso di variabili locali, una volta definito qualsiasi cosa nel metodo che rimane per sempre nella class.

Potrei pensare di seguire 2 motivi

  1. Dato che la maggior parte delle risposte è stata detta ponendo il vincolo di inizializzare la variabile locale, si garantisce che alla variabile locale venga assegnato un valore come richiesto dal programmatore e che i risultati attesi siano calcolati.
  2. Le variabili di istanza possono essere nascoste dichiarando variabili locali (stesso nome) – per garantire il comportamento previsto, le variabili locali sono forzate ad essere inizializzate in un valore. (Eviterebbe totalmente questo però)