Quando si dovrebbe usare final per i parametri del metodo e le variabili locali?


171

Ho trovato un paio di riferimenti ( ad esempio ) che suggeriscono di utilizzare finalil più possibile e mi chiedo quanto sia importante. Ciò è principalmente nel contesto dei parametri del metodo e delle variabili locali, non dei metodi o delle classi finali. Per le costanti, ha ovviamente senso.

Da un lato, il compilatore può apportare alcune ottimizzazioni e rende più chiara l'intenzione del programmatore. D'altra parte, aggiunge verbosità e le ottimizzazioni possono essere banali.

È qualcosa che dovrei fare uno sforzo per ricordare?


Ecco un post correlato di guardare oltre: stackoverflow.com/questions/137868/...
mattlant


1
Sto eseguendo l'upgrade solo perché non sapevo che era possibile utilizzare final come modificatore sui parametri prima di leggere questo. Grazie!
Kip

Risposte:


178

Ossessione per:

  • Campi finali: contrassegnare i campi come finali li costringe a essere impostati entro la fine della costruzione, rendendo immutabile quel riferimento di campo. Ciò consente la pubblicazione sicura dei campi e può evitare la necessità di sincronizzazione su letture successive. (Si noti che per un riferimento ad oggetto, solo il riferimento al campo è immutabile - le cose a cui fa riferimento il riferimento all'oggetto possono ancora cambiare e ciò influisce sull'immutabilità.)
  • Campi statici finali - Anche se ora uso enum per molti dei casi in cui utilizzavo campi finali statici.

Considera ma usa con giudizio:

  • Classi finali - Il design del framework / API è l'unico caso in cui lo considero.
  • Metodi finali - Fondamentalmente uguale alle classi finali. Se stai usando modelli di metodi come folli e segnando elementi finali, probabilmente stai facendo troppo affidamento sull'eredità e non abbastanza sulla delega.

Ignora se non ti senti anale:

  • Parametri del metodo e variabili locali - RARAMENTE lo faccio principalmente perché sono pigro e trovo che ingombra il codice. Devo ammettere pienamente che la marcatura dei parametri e delle variabili locali che non ho intenzione di modificare è "più giusta". Vorrei che fosse l'impostazione predefinita. Ma non lo è e trovo il codice più difficile da capire con le finali dappertutto. Se sono nel codice di qualcun altro, non lo estrarrò, ma se scrivo un nuovo codice non lo inserirò. Un'eccezione è il caso in cui devi contrassegnare qualcosa di definitivo in modo da poter accedere dall'interno di una classe interna anonima.

  • Modifica: si noti che un caso d'uso in cui le variabili locali finali sono effettivamente molto utili, come menzionato da @ adam-gent, è quando il valore viene assegnato al var nei rami if/ else.


8
Joshua Bloch sostiene che tutte le classi dovrebbero essere definite come finali, a meno che non siano progettate per l'ereditarietà. Sono d'accordo con lui; Aggiungo final a ogni classe che implementa un'interfaccia (per essere in grado di creare unit test). Contrassegna anche come finale tutti i metodi protetti / di classe, che non verranno sostituiti.
rmaruszewski,

61
Con tutto il rispetto per Josh Bloch (e questo è un importo considerevole), non sono d'accordo con il caso generale. Nel caso della creazione di un'API, assicurati di bloccare le cose. Bui all'interno del tuo codice, erigere muri che in seguito devi abbattere è una perdita di tempo.
Alex Miller,

9
Non è sicuramente una "perdita di tempo", soprattutto perché non costa affatto tempo ... In un'applicazione, di solito faccio quasi tutte le classi finaldi default. Tuttavia, potresti non notare i vantaggi a meno che tu non utilizzi un IDE Java veramente moderno (cioè IDEA).
Rogério,

9
IDEA ha (out of the box) centinaia di ispezioni del codice e alcune di queste sono in grado di rilevare codice non utilizzato / non necessario in finalclassi / metodi. Ad esempio, se un metodo finale dichiara di lanciare un'eccezione controllata ma non la lancia mai, IDEA te lo dirà e puoi rimuovere l'eccezione dalla throwsclausola. A volte, puoi anche trovare interi metodi inutilizzati, che è rilevabile quando non possono essere sostituiti.
Rogério

8
@rmaruszewski Contrassegnare le classi come final impedisce alla maggior parte dei framework di derisione di essere in grado di deriderle, rendendo il codice più difficile da testare. Farei un finale di classe solo se fosse di fondamentale importanza che non venga esteso.
Mike Rylander,

44

È qualcosa che dovrei fare uno sforzo per ricordarmi di fare?

No, se si utilizza Eclipse, poiché è possibile configurare un'azione di salvataggio per aggiungere automaticamente questi modificatori finali . Quindi ottieni i vantaggi con meno sforzo.


3
Ottimo consiglio con Save Action, non lo sapevo.
sanità mentale

1
Sto principalmente considerando il vantaggio che final rende il codice più sicuro dai bug assegnando accidentalmente alla variabile sbagliata, piuttosto che eventuali ottimizzazioni che possono o meno verificarsi.
Peter Hilton,

4
È davvero un problema per te? Quante volte hai effettivamente avuto un bug derivante da questo?
Alex Miller,

1
+1 per un suggerimento utile in Eclipse. Penso che dovremmo usare final il più possibile per evitare bug.
emeraldhieu,

Puoi farlo anche in IntelliJ?
Koray Tugay,

15

I vantaggi in fase di sviluppo di "final" sono significativi almeno quanto quelli in fase di esecuzione. Racconta ai futuri editor del codice qualcosa sulle tue intenzioni.

Contrassegnare una classe come "finale" indica che durante la progettazione o l'implementazione della classe non hai compiuto sforzi per gestire l'estensione in modo corretto. Se i lettori possono apportare modifiche alla classe e desiderano rimuovere il modificatore "finale", possono farlo a proprio rischio. Sta a loro assicurarsi che la classe gestisca bene l'estensione.

Contrassegnare una variabile "finale" (e assegnarla nel costruttore) è utile con l'iniezione di dipendenza. Indica la natura "collaboratore" della variabile.

Contrassegnare un metodo "finale" è utile nelle classi astratte. Delinea chiaramente dove si trovano i punti di estensione.


11

Uso finaltutto il tempo per rendere Java più basato sulle espressioni. Vedi le condizioni di Java ( if,else,switch) non sono basate sull'espressione che ho sempre odiato soprattutto se sei abituato alla programmazione funzionale (es. ML, Scala o Lisp).

Quindi dovresti provare a usare sempre (IMHO) le variabili finali quando usi le condizioni.

Lasciate che vi faccia un esempio:

    final String name;
    switch(pluginType) {
        case CANDIDATE_EXPORT:
            name = "Candidate Stuff";
            break;
        case JOB_POSTING_IMPORT:
            name = "Blah";
            break;
        default:
            throw new IllegalStateException();
    }

Ora se aggiungi un'altra caseistruzione e non imposti nameil compilatore fallirà. Il compilatore fallirà anche se non si interrompe ogni caso (che si imposta la variabile). Ciò consente di rendere Java molto simile a quello di Lisplet espressioni e di renderlo non rientrato in modo massiccio nel codice (a causa delle variabili di ambito lessicale).

E come ha notato @Recurse (ma a quanto pare -1 me) puoi fare il precedente senza fare String name finalper ottenere l'errore del compilatore (che non ho mai detto che non puoi) ma puoi facilmente fare in modo che l'errore del compilatore scompaia impostando il nome dopo lo switch istruzione che getta via l'espressione semantica o peggio dimenticando a breakcui non si può causare un errore (nonostante ciò che dice @Recurse) senza usare final:

    String name;
    switch(pluginType) {
        case CANDIDATE_EXPORT:
            name = "Candidate Stuff";
            //break; whoops forgot break.. 
            //this will cause a compile error for final ;P @Recurse
        case JOB_POSTING_IMPORT:
            name = "Blah";
            break;
    }
    // code, code, code
    // Below is not possible with final
    name = "Whoops bug";

A causa del nome dell'impostazione del bug (oltre a dimenticare a breakquale anche un altro bug) ora posso accidentalmente fare questo:

    String name;
    switch(pluginType) {
        case CANDIDATE_EXPORT:
            name = "Candidate Stuff";
            break;
        //should have handled all the cases for pluginType
    }
    // code, code, code
    // Below is not possible with final
    name = "Whoops bug";

La variabile finale forza una singola valutazione di quale nome dovrebbe essere. Simile a come una funzione che ha un valore di ritorno deve sempre restituire un valore (ignorando le eccezioni), il blocco switch nome dovrà risolvere il nome e quindi legato a quel blocco switch che rende più semplice il refactoring di blocchi di codice (cioè refactore Eclipe: metodo extract) .

Quanto sopra in OCaml:

type plugin = CandidateExport | JobPostingImport

let p = CandidateExport

let name = match p with
    | CandidateExport -> "Candidate Stuff"
    | JobPostingImport -> "Blah" ;;

Le match ... with ...Esamina come una funzione cioè espressione. Nota come appare la nostra istruzione switch.

Ecco un esempio in Scheme (Racket o Chicken):

(define name 
    (match b
      ['CandidateExport "Candidate Stuff"]
      ['JobPostingImport "Blah"]))

1
Tranne che il compilatore java ti darà già un errore "nome potenzialmente non inizializzato" e rifiuterà di compilare con o senza il finale.
Recurse del

4
Sì, ma senza il finale è possibile ripristinarlo in qualsiasi momento. Ho visto questo accadere in cui uno sviluppatore ha trasformato un else if (...)in un if(...)reimpostando così la variabile. Gli ho mostrato che non sarebbe mai successo con una variabile finale. Fondamentalmente finalti costringe ad assegnare la variabile una volta e una sola volta ... quindi: P
Adam Gent

9

Ho trovato che i parametri del metodo di marcatura e i locali finalsono utili come aiuto per il refactoring quando il metodo in questione è un pasticcio incomprensibile lungo diverse pagine. Cospargi finalliberamente, vedi quali errori "impossibile assegnare alla variabile finale" vengono generati dal compilatore (o dal tuo IDE) e potresti scoprire perché la variabile chiamata "dati" finisce nulla anche se parecchi commenti (non aggiornati) giurano che possono non succede.

Quindi è possibile correggere alcuni degli errori sostituendo le variabili riutilizzate con nuove variabili dichiarate più vicine al punto di utilizzo. Quindi scopri che puoi racchiudere intere parti del metodo tra parentesi graffe, e improvvisamente sei un IDE keypress lontano da "Metodo di estrazione" e il tuo mostro è diventato più comprensibile.

Se il tuo metodo non lo è già un disastro irrintracciabile, immagino che potrebbe essere utile rendere le cose definitive per scoraggiare le persone dal trasformarle in tali disastri; ma se è un metodo breve (vedi: non non mantenibile), rischi di aggiungere molta verbosità. In particolare, le firme delle funzioni Java sono abbastanza difficili da contenere 80 caratteri, così come è senza aggiungere altri sei per argomento!


1
Ultimo punto molto valido, anche se ho rinunciato al limite di 80 caratteri molto tempo fa, poiché la risoluzione dello schermo è cambiata un po 'negli ultimi 10 anni. Posso facilmente inserire una linea di 300 caratteri sul mio schermo senza scorrere. Tuttavia, la leggibilità è ovviamente migliore senza il finalparametro before di ogni parametro.
brimborio

8

Bene, tutto dipende dal tuo stile ... se ti piace vedere la finale quando non modificherai la variabile, allora usala. Se non ti piace vederlo ... allora lascialo fuori.

Personalmente mi piace la minore verbosità possibile, quindi tendo ad evitare l'uso di parole chiave extra che non sono realmente necessarie.

Preferisco i linguaggi dinamici, quindi probabilmente non è una sorpresa che mi piaccia evitare la verbosità.

Quindi, direi solo scegliere la direzione verso la quale ti stai inclinando e seguirla (in ogni caso, cerca di essere coerente).


Come nota a margine, ho lavorato su progetti che utilizzano entrambi e non usano tale schema, e non ho visto alcuna differenza nella quantità di bug o errori ... Non penso che sia uno schema che sarà enormemente migliorare il conteggio dei bug o altro, ma di nuovo è stile e se ti piace esprimere l'intenzione di non modificarlo, vai avanti e usalo.


6

È utile nei parametri per evitare di modificare accidentalmente il valore del parametro e introdurre un bug sottile. Uso per ignorare questa raccomandazione ma dopo aver trascorso circa 4 ore. in un metodo orribile (con centinaia di righe di codice e più fors, if annidati e ogni sorta di cattive pratiche) ti consiglierei di farlo.

 public int processSomethingCritical( final int x, final int y ){
 // hundreds of lines here 
     // for loop here...
         int x2 = 0;
        x++; // bug aarrgg...
 // hundreds of lines there
 // if( x == 0 ) { ...

 }

Naturalmente in un mondo perfetto questo non accadrà, ma ... beh ... a volte devi supportare il codice degli altri. :(


2
Questo metodo presenta problemi più gravi della mancata finale. È abbastanza raro, sebbene non impossibile, che ci sia una buona ragione per cui un metodo sia così disordinato da causare questo tipo di errori. Un piccolo pensiero messo in nomi di variabili farebbe molto per incidenti come questo.
ykaganovich,

2
Se hai "centinaia di righe di codice" in un singolo metodo, potresti voler dividerlo in diversi metodi più piccoli.
Steve Kuo,

5

Se stai scrivendo un'applicazione che qualcuno dovrà leggere il codice dopo, diciamo, 1 anno, quindi sì, usa final sulla variabile che non deve essere modificata in ogni momento. In questo modo, il tuo codice sarà più "auto-documentante" e ridurrai anche la possibilità per gli altri sviluppatori di fare cose stupide come usare una costante locale come variabile temporanea locale.

Se stai scrivendo del codice usa e getta, allora, nah, non preoccuparti di identificare tutte le costanti e renderle definitive.


4

Userò final il più possibile. In questo modo verrà contrassegnato se si modifica involontariamente il campo. Ho anche impostato i parametri del metodo su final. In questo modo ho rilevato diversi bug dal codice che ho rilevato quando provano a "impostare" un parametro dimenticando i passaggi Java per valore.


2

Non è chiaro dalla domanda se questo sia ovvio, ma rendere definitivo un parametro del parametro influenza solo il corpo del metodo. Essa non trasmettere tutte le informazioni interessanti sulla intenzioni del metodo alla invoker. L'oggetto che viene passato può ancora essere mutato all'interno del metodo (le finali non sono cons) e l'ambito della variabile rientra nel metodo.

Per rispondere alla tua precisa domanda, non mi preoccuperei di rendere un'istanza o una variabile locale (inclusi i parametri del metodo) finalizzati a meno che il codice non lo richiedesse (ad esempio la variabile sia referenziata da una classe interna), o per chiarire una logica davvero complicata.

Ad esempio, le variabili le renderei definitive se sono logicamente costanti.


2

Ci sono molti usi per la variabile final . Qui ci sono solo alcuni

Costanti finali

 public static class CircleToolsBetter {
     public final static double PI = 3.141;
        public double getCircleArea(final double radius) {
          return (Math.pow(radius, 2) * PI);
        }
    }

Questo può essere usato quindi per altre parti dei tuoi codici, o accessibile da altre classi, in questo modo se dovessi cambiare il valore non dovresti cambiarli uno per uno.

Variabili finali

public static String someMethod(final String environmentKey) {
    final String key = "env." + environmentKey;
    System.out.println("Key is: " + key);
    return (System.getProperty(key));

  }

}

In questa classe, si crea una variabile finale con ambito che aggiunge un prefisso al parametro environmentKey. In questo caso, la variabile finale è finale solo nell'ambito di esecuzione, che è diversa ad ogni esecuzione del metodo. Ogni volta che si inserisce il metodo, il finale viene ricostruito. Non appena viene costruito, non può essere modificato durante l'ambito dell'esecuzione del metodo. Ciò consente di correggere una variabile in un metodo per la durata del metodo. vedi sotto:

public class FinalVariables {


  public final static void main(final String[] args) {
    System.out.println("Note how the key variable is changed.");
    someMethod("JAVA_HOME");
    someMethod("ANT_HOME");
  }
}

Costanti finali

public double equation2Better(final double inputValue) {
    final double K = 1.414;
    final double X = 45.0;

double result = (((Math.pow(inputValue, 3.0d) * K) + X) * M);
double powInputValue = 0;         
if (result > 360) {
  powInputValue = X * Math.sin(result); 
} else {
  inputValue = K * Math.sin(result);   // <= Compiler error   
}

Questi sono particolarmente utili quando si hanno righe di codice molto lunghe e generano errori del compilatore in modo da non incorrere in errori di logica / business quando qualcuno modifica accidentalmente variabili che non dovrebbero essere modificate.

Collezioni finali

Caso diverso quando parliamo di Collezioni, è necessario impostarle come non modificabili.

 public final static Set VALID_COLORS; 
    static {
      Set temp = new HashSet( );
      temp.add(Color.red);
      temp.add(Color.orange);
      temp.add(Color.yellow);
      temp.add(Color.green);
      temp.add(Color.blue);
      temp.add(Color.decode("#4B0082")); // indigo
      temp.add(Color.decode("#8A2BE2")); // violet
      VALID_COLORS = Collections.unmodifiableSet(temp);
    }

in caso contrario, se non lo si imposta come non modificabile:

Set colors = Rainbow.VALID_COLORS;
colors.add(Color.black); // <= logic error but allowed by compiler

Le classi finali e i metodi finali non possono essere estesi o sovrascritti rispettivamente.

MODIFICA: PER INDIRIZZARE IL PROBLEMA DELLA CLASSE FINALE PER QUANTO RIGUARDA L'ECCAPSULAZIONE:

Esistono due modi per rendere finale una classe. Il primo è utilizzare la parola chiave final nella dichiarazione di classe:

public final class SomeClass {
  //  . . . Class contents
}

Il secondo modo per rendere definitiva una classe è dichiarare privati ​​tutti i suoi costruttori:

public class SomeClass {
  public final static SOME_INSTANCE = new SomeClass(5);
  private SomeClass(final int value) {
  }

Contrassegnarlo come finale ti risparmia il problema se scopri che si tratta di un finale, per dimostrare un'occhiata a questa classe di test. sembra pubblico a prima vista.

public class Test{
  private Test(Class beanClass, Class stopClass, int flags)
    throws Exception{
    //  . . . snip . . . 
  }
}

Sfortunatamente, poiché l'unico costruttore della classe è privato, è impossibile estenderla. Nel caso della classe Test, non vi è alcun motivo per cui la classe debba essere definitiva. La classe Test è un buon esempio di come le classi finali implicite possano causare problemi.

Quindi dovresti segnarlo come finale quando rendi implicitamente una classe finale rendendola privata come costruttore.


1

Un po 'un compromesso, come dici tu, ma preferisco l'uso esplicito di qualcosa rispetto all'uso implicito. Ciò contribuirà a rimuovere alcune ambiguità per i futuri manutentori del codice, anche se sei solo tu.


1

Se si dispone di classi interne (anonime) e il metodo deve accedere alla variabile del metodo contenitore, è necessario disporre di tale variabile come finale.

A parte questo, quello che hai detto è giusto.


Ora java 8 offre flessibilità con variabili finali efficaci.
Ravindra babu,

0

Usa la finalparola chiave per una variabile se stai facendo quella variabile comeimmutable

Dichiarando la variabile come definitiva, aiuta gli sviluppatori a escludere possibili problemi di modifica delle variabili in un ambiente altamente multi-thread.

Con la versione java 8, abbiamo un altro concetto chiamato " effectively final variable". Una variabile non finale può apparire come variabile finale.

le variabili locali a cui fa riferimento un'espressione lambda devono essere definitive o effettivamente definitive

Una variabile è considerata effettiva se non viene modificata dopo l'inizializzazione nel blocco locale. Ciò significa che ora puoi utilizzare la variabile locale senza la parola chiave finale all'interno di una classe anonima o di un'espressione lambda, a condizione che debbano essere effettivamente finali.

Fino a Java 7, non è possibile utilizzare una variabile locale non finale all'interno di una classe anonima, ma da Java 8 è possibile

Dai un'occhiata a questo articolo


-1

Prima di tutto, la parola chiave finale viene utilizzata per creare una costante variabile. Costante significa che non cambia. Per esempio:

final int CM_PER_INCH = 2.54;

Dichiareresti la variabile finale perché un centimetro per pollice non cambia.

Se si tenta di sovrascrivere un valore finale, la variabile è quella che è stata dichiarata per prima. Per esempio:

final String helloworld = "Hello World";
helloworld = "A String"; //helloworld still equals "Hello World"

C'è un errore di compilazione simile a:

local variable is accessed from inner class, must be declared final

Se la tua variabile non può essere dichiarata final o se non vuoi dichiararla final prova questo:

final String[] helloworld = new String[1];
helloworld[0] = "Hello World!";
System.out.println(helloworld[0]);
helloworld[0] = "A String";
System.out.println(helloworld[0]);

Questo stamperà:

Hello World!
A String
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.