it-swarm.it

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

Java consente di contrassegnare le variabili (campi/locali/parametri) come final, per impedire la riassegnazione in esse. Lo trovo molto utile con i campi, poiché 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 final anche se non verranno mai riassegnati (con l'eccezione ovvia quando devono essere usato 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 final ovunque, qual è lo stile industriale più comune e perché.

114
Oak

Uso final allo 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, ognuno facendo una singola attività. 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, final non garantisce che non puoi modificare il valore/lo stato di una variabile (non primaria). Solo che non puoi riassegnare il riferimento a quell'oggetto una volta inizializzato. In altre parole, funziona perfettamente solo con variabili di tipo primitivo o immutabile. Prendere in considerazione

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.

68
Péter Török

Il tuo Java lo stile di programmazione 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 parola chiave final. Afferma che TU sai che non verrà mai riassegnato, ma nessun altro lo sa. L'uso di final chiarisce immediatamente il tuo codice un po 'di più.

66
J.K.

Uno dei vantaggi dell'utilizzo di final/const laddove 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.

32
LennyProgrammers

Considero final nei parametri del metodo e nelle variabili locali come rumore di codice. Java possono essere piuttosto lunghe (specialmente con i generici) - non è necessario renderle più lunghe.

Se i test unitari sono scritti correttamente, l'assegnazione a parametri "dannosi" verrà presa, quindi non dovrebbe mai essere in realtà un problema. La chiarezza visiva è più importante che evitare un bug possibile 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 su parametri o variabili locali, se ti preoccupi profondamente di tali cose.

Ovviamente, se hai bisogno per renderli definitivi, ad esempio perché stai usando il valore in una classe anonima, allora nessun problema: questa è la soluzione più semplice e pulita.

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 i parametri final impediscono al programmatore di aggiungere codice più in basso dal pensare che stia lavorando con il valore originale, c'è lo stesso rischio che il codice più in basso possa usare input invece di lowercaseInput , che non dovrebbe e da cui non è possibile proteggerlo, perché non è possibile escluderlo dall'ambito di applicazione (o persino assegnare null a input se 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 hit di prestazioni di dover richiamare 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é un altro codice potrebbe 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, efficacemente sostituendolo per 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 final in realtà 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.

22
Bohemian

Lascio che Eclipse metta final prima 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.

7
maaartinus