In Java, dovrei usare "final" per parametri e locali anche quando non è necessario?


105

Java consente di contrassegnare le variabili (campi / locali / parametri) come final, per evitare di riassegnarle. Lo trovo molto utile con i campi, in quanto mi aiuta a vedere rapidamente se alcuni attributi - o un'intera classe - sono fatti per essere immutabili.

D'altra parte, lo trovo molto meno utile con i locali e i parametri, e di solito evito di contrassegnarli come finalanche se non verranno mai riassegnati in (con l'ovvia eccezione quando devono essere utilizzati in una classe interna) . Ultimamente, tuttavia, ho trovato il codice che utilizzava final ogni volta che poteva, che suppongo tecnicamente fornisca ulteriori informazioni.

Non sono più sicuro del mio stile di programmazione, mi chiedo quali siano altri vantaggi e svantaggi dell'applicazione finalovunque, qual è lo stile industriale più comune e perché.


Le domande in stile codice @Amir sembrano appartenere qui meglio che a SO, e non ho trovato alcuna politica nelle FAQ o nella meta di questo sito riguardo a questo. Potete per favore dirigermi?
Oak,

1
@Oak Dice: "Problema di programmazione specifico, algoritmi software, codifica , chiedere su Stack Overflow"
Amir Rezaei

7
@Amir Non sono d'accordo, non vedo come questo sia un problema di codifica. In ogni caso questo dibattito non appartiene qui, quindi ho aperto un meta-argomento su questo tema .
Oak,

3
@amir questo è totalmente in argomento per questo sito, in quanto è una domanda soggettiva sulla programmazione - vedi / faq
Jeff Atwood,

1
Per quanto riguarda le variabili locali: i compilatori Java più vecchi prestavano attenzione al fatto che tu abbia dichiarato un locale finalo meno e ottimizzato di conseguenza. I compilatori moderni sono abbastanza intelligenti da capirlo da soli. Almeno sulle variabili locali, finalè strettamente a beneficio dei lettori umani. Se le tue routine non sono troppo complicate, la maggior parte dei lettori umani dovrebbe essere in grado di capirlo anche da solo.
Solomon Slow

Risposte:


67

Uso finalallo stesso modo di te. Per me sembra superfluo sulle variabili locali e sui parametri del metodo e non fornisce utili informazioni extra.

Una cosa importante è che si sforzano di mantenere i miei metodi brevi e puliti , facendo ciascuno un singolo compito. Pertanto le mie variabili e parametri locali hanno un ambito molto limitato e sono utilizzate solo per un unico scopo. Ciò riduce al minimo le possibilità di riassegnarle inavvertitamente.

Inoltre, come sicuramente saprai, finalnon garantisce che non puoi modificare il valore / lo stato di una variabile (non primaria). Solo che non è possibile riassegnare il riferimento a quell'oggetto una volta inizializzato. In altre parole, funziona perfettamente solo con variabili di tipo primitivo o immutabile. Tener conto di

final String s = "forever";
final int i = 1;
final Map<String, Integer> m = new HashMap<String, Integer>();

s = "never"; // compilation error!
i++; // compilation error!
m.put(s, i); // fine

Ciò significa che in molti casi non rende ancora più facile capire cosa succede all'interno del codice e l'incomprensione può in effetti causare bug sottili che sono difficili da rilevare.


1
Per quanto riguarda la modifica - sono consapevole della semantica di final, grazie :) ma buon punto sui metodi brevi e puliti - immagino che se il metodo è abbastanza breve da cui è ovvio che una variabile non viene riassegnata, c'è ancora meno motivo per considerare la finalparola chiave.
Oak,

Non sarebbe bello se potessimo avere i parametri finali e le variabili locali e ancora una sintassi breve e chiara? programmers.stackexchange.com/questions/199783/…
oberlies

7
"final non garantisce che non è possibile modificare il valore / lo stato di una variabile (non primaria). Solo che non è possibile riassegnare il riferimento a quell'oggetto una volta inizializzato." Nonprimitive = reference (gli unici tipi in Java sono tipi primitivi e tipi di riferimento). Il valore di una variabile di tipo riferimento è un riferimento. Pertanto, non è possibile riassegnare il riferimento = non è possibile modificare il valore.
user102008

2
+1 per la trappola di riferimento / stato. Si noti tuttavia che la marcatura delle variabili finali potrebbe essere necessaria per creare chiusure nei nuovi aspetti funzionali di Java8
Newtopian,

Il tuo confronto è parziale come sottolineato da @ user102008. L'assegnazione delle variabili non è la stessa del suo aggiornamento del valore
ericn,

64

Il tuo stile di programmazione Java e i pensieri vanno bene, non c'è bisogno di dubitare di te.

D'altra parte, lo trovo molto meno utile con i locali e i parametri, e di solito evito di contrassegnarli come finali anche se non verranno mai riassegnati in (con l'ovvia eccezione quando devono essere utilizzati in una classe interna ).

Questo è esattamente il motivo per cui dovresti usare la finalparola chiave. Lei afferma che TU sai che sarà mai essere ri-assegnato, ma nessun altro lo sa. L'uso finaldisambigua immediatamente il tuo codice un po 'di più.


8
Se il tuo metodo è chiaro e fa solo una cosa, anche il lettore lo saprà. L'ultima parola rende il codice illeggibile. E se è illeggibile è molto più ambiguo
eddieferetro

5
@eddieferetro In disaccordo. La parola chiave finalindica l'intento, il che rende il codice più leggibile. Inoltre, una volta che avrai a che fare con il codice del mondo reale, noterai che è raramente chiaro e chiaro e l'aggiunta libera di finals ovunque ti aiuterà a individuare i bug e a capire meglio il codice legacy.
Andres F.

4
L '"intento" che la variabile non cambierà mai e che il codice si basa su questo fatto ? O è l'intento "Accade così che questa variabile non cambi mai, quindi la segnerò come definitiva". Il secondo è rumore inutile e forse dannoso. E dal momento che sembri sostenere la marcatura di tutti i locali, stai facendo il dopo. Grande -1.
user949300,

2
finaldichiara l'intento, che di solito è una buona cosa in un pezzo di codice, ma ha il prezzo di aggiungere disordine visivo. Come la maggior parte delle cose nella programmazione, c'è un compromesso qui: sta esprimendo il fatto che la tua variabile verrà utilizzata solo una volta che vale la verbosità aggiunta?
christopheml,

Il disordine visivo sarà notevolmente ridotto quando si utilizza l'evidenziazione della sintassi. Contrassegno i miei locali che sono assegnati una sola volta - e a cui voglio essere assegnato una sola volta - con final. Potrebbe essere la maggior parte dei locali, ma chiaramente non tutti . Per me non c'è "capita di non cambiare mai". Ci sono 2 tipi chiaramente distinti di gente del posto nel mio mondo. Quelli che non dovrebbero mai cambiare di nuovo (finali) e quelli che devono cambiare in base a ciò che il compito a portata di mano impone (di cui ci sono pochissimi, rendendo le funzioni abbastanza facili da ragionare).
blubberdiblub

31

Uno dei vantaggi dell'utilizzo final/ constove possibile è che riduce il carico mentale per il lettore del codice.

Può essere certo che il valore / riferimento non verrà mai modificato in seguito. Quindi non è necessario prestare attenzione alle modifiche per comprendere il calcolo.

Ho cambiato idea su questo dopo aver appreso linguaggi di programmazione puramente funzionali. Ragazzo, che sollievo, se puoi fidarti di una "variabile" per mantenere sempre il suo valore iniziale.


16
Bene, in Java finalnon garantisce che non è possibile modificare il valore / lo stato di una variabile (non primaria). Solo che non è possibile riassegnare il riferimento a quell'oggetto una volta inizializzato.
Péter Török,

9
Lo so, ecco perché ho distinto tra valore e riferimento. Il concetto è il più utile nel contesto di strutture di dati immutabili e / o pura funzionalità.
LennyProgrammers,

7
@ PéterTörök È immediatamente utile con i tipi primitivi e in qualche modo utile con riferimenti a oggetti mutabili (almeno sai che hai sempre a che fare con lo stesso oggetto!). È immensamente utile quando si tratta di codice progettato per essere immutabile da zero.
Andres F.

18

Considero i finalparametri del metodo e le variabili locali come rumore da codice. Le dichiarazioni del metodo Java possono essere piuttosto lunghe (specialmente con i generici) - non è necessario renderle più lunghe.

Se i test unitari vengono scritti correttamente, l'assegnazione a parametri "dannosi" verrà presa, quindi non dovrebbe mai essere effettivamente un problema. La chiarezza visiva è più importante che evitare un possibile bug che non viene rilevato perché i test delle unità hanno una copertura insufficiente.

Strumenti come FindBugs e CheckStyle che possono essere configurati per interrompere la compilazione se l'assegnazione viene effettuata a parametri o variabili locali, se ti interessano profondamente di queste cose.

Naturalmente, se avete bisogno di farli finale, ad esempio perché si sta utilizzando il valore in una classe anonima, quindi non è un problema - che è la soluzione più pulita più semplice.

Oltre all'ovvio effetto dell'aggiunta di parole chiave extra ai tuoi parametri e quindi alla mimetizzazione di IMHO, l'aggiunta di parametri finali ai parametri del metodo può spesso rendere meno leggibile il codice nel corpo del metodo, il che peggiora il codice - essere "buono", codice deve essere il più leggibile e il più semplice possibile. Per un esempio inventato, supponiamo che io abbia un metodo che deve lavorare in modo insensibile al caso.

Senza final:

public void doSomething(String input) {
    input = input.toLowerCase();
    // do a few things with input
}

Semplice. Pulito. Tutti sanno cosa sta succedendo.

Ora con 'final', opzione 1:

public void doSomething(final String input) {
    final String lowercaseInput = input.toLowerCase();
    // do a few things with lowercaseInput
}

Mentre la creazione dei parametri finalimpedisce al programmatore di aggiungere codice più in basso dal pensare che stia lavorando con il valore originale, c'è un rischio uguale che il codice più in basso potrebbe usare al inputposto di lowercaseInput, che non dovrebbe e da cui non può essere protetto, perché puoi " t prenderlo dal campo di applicazione (o anche assegnare nulla inputse ciò potrebbe aiutare comunque).

Con 'final', opzione 2:

public void doSomething(final String input) {
    // do a few things with input.toLowerCase()
}

Ora abbiamo appena creato ancora più rumore di codice e introdotto un successo in termini di prestazioni che devono invocare toLowerCase()n volte.

Con 'final', opzione 3:

public void doSomething(final String input) {
    doSomethingPrivate(input.toLowerCase());
}

/** @throws IllegalArgumentException if input not all lower case */
private void doSomethingPrivate(final String input) {
    if (!input.equals(input.toLowerCase())) {
        throw new IllegalArgumentException("input not lowercase");
    }
    // do a few things with input
}

Parla del rumore del codice. Questo è un disastro ferroviario. Abbiamo un nuovo metodo, un blocco di eccezioni richiesto, perché altri codici potrebbero invocarlo in modo errato. Più unit test per coprire l'eccezione. Tutto per evitare una linea semplice, preferibile e innocua per IMHO.

C'è anche il problema che i metodi non dovrebbero essere così lunghi che non è possibile visualizzarli facilmente e sapere a colpo d'occhio che un'assegnazione al parametro ha avuto luogo.

Penso che sia una buona pratica / stile che se si assegna a un parametro lo si fa all'inizio del metodo, preferibilmente prima riga o subito dopo il controllo di input di base, sostituendolo efficacemente per l' intero metodo, che ha un effetto coerente all'interno del metodo. I lettori sanno di aspettarsi che qualsiasi compito sia ovvio (vicino alla dichiarazione della firma) e in un posto coerente, il che mitiga notevolmente il problema che l'aggiunta di final sta cercando di evitare. In realtà raramente lo assegno ai parametri, ma se lo faccio lo faccio sempre all'inizio di un metodo.


Nota anche che in finalrealtà non ti protegge come potrebbe sembrare a prima vista:

public void foo(final Date date) {
    date.setTime(0); 
    // code that uses date
}

final non ti protegge completamente a meno che il tipo di parametro non sia primitivo o immutabile.


Nell'ultimo caso, finalfornisce la garanzia parziale che hai a che fare con la stessa Dateistanza. Alcune garanzie sono meglio di niente (semmai, stai discutendo per classi immutabili da zero!). In ogni caso, in pratica gran parte di ciò che dici dovrebbe influire sulle lingue esistenti immutabili per impostazione predefinita, ma non è così, il che significa che è un problema.
Andres F.

+1 per indicare il rischio "quel codice più in basso può usare input". Preferirei comunque che i miei parametri fossero final, con la possibilità di renderli non definitivi per casi come sopra. Non vale la pena spammare la firma con una parola chiave.
maaartinus,

Trovo il punto che si potrebbe erroneamente usare al inputposto del lowercaseInputmoot. Sebbene tecnicamente corretto, è facile individuare un errore una volta che ti causa un bug, poiché puoi vedere chiaramente la diversa semantica delle 2 variabili nominate separatamente nel loro punto di utilizzo (a condizione che tu dia loro un buon nome). Per me, è più pericoloso confondere 2 usi semanticamente diversi di valori separati nella stessa variabile e quindi lo stesso nome. Può rendere più difficile rintracciare i bug, poiché dovrai leggere e comprendere tutto il codice precedente che è in qualche modo correlato alla variabile 1.
blubberdiblub

7

Lascio che eclipse metta finalprima di ogni variabile locale, poiché lo considero per rendere il programma più facile da leggere. Non ce la faccio con i parametri, poiché voglio mantenere l'elenco dei parametri il più breve possibile, idealmente dovrebbe rientrare in una riga.


2
Inoltre, per i parametri, è possibile fare in modo che il compilatore emetta un avviso o un errore se viene assegnato un parametro.
oberlies,

3
Al contrario, trovo più difficile da leggere perché ingombra il codice.
Steve Kuo,

3
@Steve Kuo: Mi permette di trovare rapidamente tutte le variabili. nessun grande guadagno, ma insieme alla prevenzione di incarichi accidentali vale i 6 caratteri. Sarei molto più felice se ci fosse qualcosa di simile varper la marcatura di variabili non finali. YMMV.
maaartino

1
@maaartinus D'accordo var! Sfortunatamente non è l'impostazione predefinita in Java e ora è troppo tardi per cambiare la lingua. Pertanto, sono disposto a sopportare il piccolo inconveniente di scrivere final:)
Andres F.

1
@maaartinus Concordato. Trovo che circa l'80-90% delle mie variabili (locali) non debba essere modificato dopo l'inizializzazione. Quindi, l'80-90% di loro ha la finalparola chiave anteposta, mentre solo il 10-20% avrebbe bisogno di un var...
JimmyB
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.