In Java, dovrei usare "final" per i parametri e la gente del posto anche quando non devo?

96

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

D'altro canto, 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'ovvia eccezione quando devono essere usato in una classe interiore). Ultimamente, tuttavia, mi sono imbattuto nel codice che utilizzava il finale ogni volta che poteva, il che suppongo tecnicamente fornisca più informazioni.

Non ho più fiducia nel mio stile di programmazione, mi chiedo quali sono altri vantaggi e svantaggi dell'applicazione di final ovunque, qual è lo stile di settore più comune e perché.

    
posta Oak 16.02.2011 - 09:23
fonte

5 risposte

61

Uso final allo stesso modo di te. Per me sembra superfluo sulle variabili locali e sui parametri del metodo, e non trasmette informazioni extra utili.

Una cosa importante è sforzarsi di mantenere i miei metodi brevi e puliti , ognuno facendo una singola attività. Pertanto le variabili e i parametri locali hanno un ambito molto limitato e vengono utilizzati solo per uno scopo. Ciò minimizza le possibilità di riassegnarli inavvertitamente.

Inoltre, come sicuramente sai, final non garantisce che non puoi cambiare il valore / stato di una variabile (non primitiva). Solo che non è possibile riassegnare reference a quell'oggetto una volta inizializzato. In altre parole, funziona perfettamente solo con variabili di tipi primitivi o immutabili. Considerare

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

Questo significa che in molti casi non è ancora facile capire cosa succede all'interno del codice e l'equivoco potrebbe causare bug sottili che sono difficili da rilevare.

    
risposta data 16.02.2011 - 09:28
fonte
57

Il tuo stile di programmazione Java e i tuoi pensieri vanno bene, non devi dubitare di te.

On the other hand, I find it a lot less useful with locals and parameters, and usually I avoid marking them as final even if they will never be re-assigned into (with the obvious exception when they need to be used in an inner class).

Questo è esattamente il motivo per cui dovresti utilizzare la parola chiave final . Dichiari che TU sa che non verrà mai riassegnato, ma nessun altro lo sa. L'utilizzo di final disambigura immediatamente il tuo codice un po 'di più.

    
risposta data 16.02.2011 - 09:44
fonte
29

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

Può essere certo che il valore / riferimento non verrà mai modificato in seguito. Quindi non ha bisogno di prestare attenzione alle modifiche per capire il calcolo.

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

    
risposta data 16.02.2011 - 09:36
fonte
15

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

Se i test unitari sono scritti correttamente, l'assegnazione ai parametri "dannosi" verrà rilevata, quindi non dovrebbe mai effettivamente essere un problema. La chiarezza visiva è più importante che evitare un errore possibile che non viene rilevato perché i test dell'unità hanno 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 interessa molto di queste cose.

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

Oltre all'ovvio effetto di aggiungere parole chiave extra ai parametri, e quindi IMHO mimetizzandoli, l'aggiunta di parametri finali ai metodi spesso rende il codice nel corpo del metodo meno leggibile, il che rende il codice peggiore - per essere "buono" ", il codice deve essere leggibile e il più semplice possibile. Per un esempio forzato, diciamo che ho un metodo che ha bisogno di lavorare senza distinzione tra maiuscole e minuscole.

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
}

Pur facendo in modo che i parametri final interrompano l'aggiunta del codice da parte del programmatore che sta 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 che non può essere protetto, perché non puoi toglierlo dal campo di applicazione (o anche assegnare null a input se ciò sarebbe di aiuto in ogni caso).

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 calo di prestazioni dovuto al fatto 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é altri codici potrebbero invocarlo in modo errato. Altri test unitari per coprire l'eccezione. Tutto per evitare una linea semplice, e IMHO preferibile e innocua.

C'è anche il problema che i metodi non dovrebbero essere così lunghi che non puoi facilmente visualizzarli visivamente e sapere a colpo d'occhio che un'assegnazione ai parametri è avvenuta.

Penso che sia una buona pratica / stile che se si assegna ad un parametro lo si fa ogni volta nel metodo, preferibilmente in prima linea o subito dopo il controllo di input di base, in modo efficace in sostituzione per il intero metodo, che ha un effetto coerente all'interno del metodo. I lettori sanno di aspettarsi che ogni incarico sia ovvio (vicino alla dichiarazione della firma) e in un luogo coerente, il che riduce notevolmente il problema che l'aggiunta di final sta cercando di evitare. In realtà raramente 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.

    
risposta data 26.08.2016 - 03:28
fonte
7

Lascio eclissi mettere final prima di ogni variabile locale, poiché lo considero per rendere il programma più facile da leggere. Non lo faccio con i parametri, dal momento che voglio mantenere la lista dei parametri più corta possibile, idealmente dovrebbe inserirsi in una sola riga.

    
risposta data 16.02.2011 - 10:35
fonte

Leggi altre domande sui tag