Quand faut-il utiliser final pour les paramètres de méthode et les variables locales?


171

J'ai trouvé quelques références ( par exemple ) qui suggèrent d'en utiliser finalautant que possible et je me demande à quel point c'est important. Ceci est principalement dans le contexte des paramètres de méthode et des variables locales, pas des méthodes ou des classes finales. Pour les constantes, cela a un sens évident.

D'une part, le compilateur peut faire quelques optimisations et cela rend l'intention du programmeur plus claire. D'autre part, cela ajoute de la verbosité et les optimisations peuvent être triviales.

Est-ce quelque chose que je devrais faire un effort pour me souvenir?


Voici un article connexe à examiner: stackoverflow.com/questions/137868/…
mattlant


1
Je vote juste parce que je ne savais pas qu'il était possible d'utiliser final comme modificateur sur les paramètres avant de lire ceci. Merci!
Kip le

Réponses:


178

Obséder pour:

  • Champs finaux - Le marquage des champs comme finaux les oblige à être définis à la fin de la construction, ce qui rend cette référence de champ immuable. Cela permet une publication sûre des champs et peut éviter le besoin de synchronisation sur les lectures ultérieures. (Notez que pour une référence d'objet, seule la référence de champ est immuable - les choses auxquelles la référence d'objet fait référence peuvent encore changer et cela affecte l'immuabilité.)
  • Champs statiques finaux - Bien que j'utilise des enums maintenant pour de nombreux cas où j'utilisais des champs finaux statiques.

Considérez mais utilisez judicieusement:

  • Classes finales - La conception du framework / API est le seul cas où je la considère.
  • Méthodes finales - Fondamentalement identiques aux classes finales. Si vous utilisez des modèles de méthode de modèle comme fou et que vous marquez des choses comme étant définitives, vous comptez probablement trop sur l'héritage et pas assez sur la délégation.

Ignorer à moins de se sentir anal:

  • Paramètres de méthode et variables locales - JE le fais RAREMENT parce que je suis paresseux et que je trouve que cela encombre le code. J'admets pleinement que le marquage des paramètres et des variables locales que je ne vais pas modifier est "plus juste". Je souhaite que ce soit la valeur par défaut. Mais ce n'est pas le cas et je trouve le code plus difficile à comprendre avec les finales partout. Si je suis dans le code de quelqu'un d'autre, je ne vais pas les retirer, mais si j'écris un nouveau code, je ne les insérerai pas. Une exception est le cas où vous devez marquer quelque chose comme final pour pouvoir y accéder depuis une classe interne anonyme.

  • Edit: notez qu'un cas d'utilisation où les variables locales finales sont en fait très utiles comme mentionné par @ adam-gent est lorsque la valeur est assignée à la var dans les branches if/ else.


8
Joshua Bloch soutient que toutes les classes devraient être définies comme finales, à moins qu'elles ne soient conçues pour l'héritage. Je suis d'accord avec lui; J'ajoute final à chaque classe qui implémente une interface (pour pouvoir créer des tests unitaires). Marquez également comme finales toutes les méthodes protégées / de classe, qui ne seront pas écrasées.
rmaruszewski

61
Avec tout le respect que je dois à Josh Bloch (et c'est une quantité considérable), je ne suis pas d'accord pour le cas général. Dans le cas de la création d'une API, verrouillez bien les choses. Bui à l'intérieur de votre propre code, ériger des murs que vous devrez ensuite démolir est une perte de temps.
Alex Miller

9
Ce n'est certainement pas une "perte de temps", surtout parce que cela ne coûte pas du tout de temps ... Dans une application, je fais normalement presque toutes les classes finalpar défaut. Vous ne remarquerez peut-être pas les avantages à moins d'utiliser un IDE Java vraiment moderne (c'est-à-dire IDEA).
Rogério

9
IDEA a (prêt à l'emploi) des centaines d'inspections de code, et certaines d'entre elles peuvent détecter du code inutilisé / inutile dans les finalclasses / méthodes. Par exemple, si une méthode finale déclare lever une exception vérifiée mais ne la lance jamais réellement, IDEA vous le dira et vous pourrez supprimer l'exception de la throwsclause. Parfois, vous pouvez également trouver des méthodes entières inutilisées, ce qui est détectable lorsqu'elles ne peuvent pas être remplacées.
Rogério

8
@rmaruszewski Marquer les classes comme finales empêche la plupart des frameworks moqueurs de pouvoir se moquer d'eux, ce qui rend le code plus difficile à tester. Je ne ferais une finale de classe que s'il était extrêmement important qu'elle ne soit pas prolongée.
Mike Rylander

44

Est-ce quelque chose que je devrais faire un effort pour ne pas oublier de faire?

Non, si vous utilisez Eclipse, car vous pouvez configurer une action de sauvegarde pour ajouter automatiquement ces modificateurs finaux pour vous. Ensuite, vous obtenez les avantages pour moins d'efforts.


3
Bon conseil avec l'action Save, je ne savais pas à ce sujet.
sanity

1
Je considère principalement l'avantage que final rend le code plus sûr des bogues en attribuant accidentellement à la mauvaise variable, plutôt que des optimisations qui peuvent ou non se produire.
Peter Hilton le

4
Est-ce vraiment un problème pour vous? Combien de fois avez-vous eu un bug qui en résultait?
Alex Miller

1
+1 pour un conseil utile dans Eclipse. Je pense que nous devrions utiliser final autant que possible pour éviter les bugs.
emeraldhieu

Pouvez-vous également faire cela dans IntelliJ?
Koray Tugay

15

Les avantages de «final» au moment du développement sont au moins aussi importants que les avantages à l'exécution. Il informe les futurs éditeurs du code de vos intentions.

Marquer une classe comme "finale" indique que vous n'avez pas fait d'effort lors de la conception ou de l'implémentation de la classe pour gérer l'extension correctement. Si les lecteurs peuvent apporter des modifications à la classe, et veulent supprimer le modificateur "final", ils peuvent le faire à leurs propres risques. C'est à eux de s'assurer que la classe gérera bien l'extension.

Marquer une variable "finale" (et l'affecter dans le constructeur) est utile avec l'injection de dépendances. Il indique le caractère «collaborateur» de la variable.

Marquer une méthode comme "finale" est utile dans les classes abstraites. Il délimite clairement où se trouvent les points d'extension.


11

J'utilise finaltout le temps pour rendre Java plus basé sur l'expression. Voir les conditions de Java ( if,else,switch) ne sont pas basées sur des expressions, ce que j'ai toujours détesté surtout si vous êtes habitué à la programmation fonctionnelle (c'est-à-dire ML, Scala ou Lisp).

Ainsi, vous devriez essayer de toujours (IMHO) utiliser les variables finales lors de l'utilisation des conditions.

Laisse moi te donner un exemple:

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

Maintenant, si ajoutez une autre caseinstruction et ne définissez pas, namele compilateur échouera. Le compilateur échouera également si vous n'interrompez pas tous les cas (que vous définissez la variable). Cela vous permet de rendre Java très similaire aux letexpressions de Lisp et de faire en sorte que votre code ne soit pas massivement indenté (à cause des variables de portée lexicale).

Et comme @Recurse l'a noté (mais apparemment -1 moi), vous pouvez faire ce qui précède sans faire String name finalpour obtenir l'erreur du compilateur (ce que je n'ai jamais dit que vous ne pouviez pas) mais vous pourriez facilement faire disparaître l'erreur du compilateur en définissant le nom après le commutateur déclaration qui jette la sémantique de l'expression ou pire oublie breakauquel vous ne pouvez pas provoquer d'erreur (malgré ce que dit @Recurse) sans utiliser 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";

En raison du nom du paramètre de bogue (en plus d'oublier à breakquel autre bogue), je peux maintenant faire ceci accidentellement:

    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 variable finale force une seule évaluation de ce que le nom devrait être. Similaire à la façon dont une fonction qui a une valeur de retour doit toujours renvoyer une valeur (en ignorant les exceptions), le bloc de commutation de nom devra résoudre le nom et donc lié à ce bloc de commutation, ce qui facilite la refactorisation de morceaux de code (par exemple, refactor Eclipe: méthode d'extraction) .

Ce qui précède dans OCaml:

type plugin = CandidateExport | JobPostingImport

let p = CandidateExport

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

L' match ... with ...évaluation comme une fonction c'est-à-dire une expression. Remarquez à quoi cela ressemble à notre instruction switch.

Voici un exemple de schéma (raquette ou poulet):

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

1
Sauf que le compilateur java vous donnera déjà une erreur "nom potentiellement non initialisé" et refusera de compiler avec ou sans le final.
Recurse

4
Oui, mais sans la finale, vous pouvez le réinitialiser à tout moment. J'ai en fait vu cela se produire lorsqu'un développeur a transformé un else if (...)en un if(...)et ainsi réinitialisé la variable. Je lui ai montré que cela ne serait jamais arrivé avec une dernière variable. Fondamentalement final, vous oblige à affecter la variable une et une seule fois ... donc: P
Adam Gent

9

J'ai trouvé des paramètres de méthode de marquage et des locaux finalutiles comme une aide à la refactorisation lorsque la méthode en question est un désordre incompréhensible de plusieurs pages. Saupoudrez finalgénéreusement, voyez quelles erreurs "ne peut pas affecter à la variable finale" le compilateur (ou votre IDE) lève, et vous découvrirez peut-être pourquoi la variable appelée "data" finit par être nulle même si plusieurs commentaires (obsolètes) jurent que cela peut ça n'arrive pas.

Ensuite, vous pouvez corriger certaines des erreurs en remplaçant les variables réutilisées par de nouvelles variables déclarées plus proches du point d'utilisation. Ensuite, vous trouvez que vous pouvez envelopper des parties entières de la méthode dans des accolades de portée, et tout à coup vous êtes à une pression de touche IDE loin de "Extraire la méthode" et votre monstre est devenu plus compréhensible.

Si votre méthode n'est pas déjà une épave impossible à entretenir, je suppose qu'il pourrait être utile de rendre les choses définitives pour décourager les gens de les transformer en épave; mais si c'est une méthode courte (voir: pas impossible à maintenir) alors vous risquez d'ajouter beaucoup de verbosité. En particulier, les signatures de fonction Java sont suffisamment difficiles pour contenir 80 caractères sans en ajouter six de plus par argument!


1
Dernier point très valable, même si j'ai abandonné la limite de 80 caractères il y a longtemps car la résolution de l'écran a un peu changé au cours des 10 dernières années. Je peux facilement insérer une ligne de 300 caractères sur mon écran sans faire défiler. Néanmoins, la lisibilité est bien sûr meilleure sans le finalparamètre avant chaque.
brimborium

8

Eh bien, tout dépend de votre style ... si vous AIMEZ voir la finale alors que vous ne modifierez pas la variable, utilisez-la. Si vous N'AIMEZ PAS le voir ... alors laissez-le de côté.

Personnellement, j'aime le moins de verbosité possible, j'ai donc tendance à éviter d'utiliser des mots clés supplémentaires qui ne sont pas vraiment nécessaires.

Je préfère les langages dynamiques, donc ce n'est probablement pas une surprise que j'aime éviter la verbosité.

Donc, je dirais simplement de choisir la direction dans laquelle vous vous penchez et de suivre (quel que soit le cas, essayez d'être cohérent).


En remarque, j'ai travaillé sur des projets qui utilisent et n'utilisent pas un tel modèle, et je n'ai vu aucune différence dans la quantité de bogues ou d'erreurs ... Je ne pense pas que ce soit un modèle qui va énormément améliorez votre nombre de bogues ou quoi que ce soit, mais encore une fois, c'est du style, et si vous aimez exprimer l'intention de ne pas le modifier, alors continuez et utilisez-le.


6

Il est utile dans les paramètres d'éviter de changer la valeur du paramètre par accident et d'introduire un bug subtil. J'ignore cette recommandation mais après avoir passé environ 4 heures. dans une méthode horrible (avec des centaines de lignes de code et de multiples fors, des if imbriqués et toutes sortes de mauvaises pratiques), je vous recommanderais de le faire.

 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 ) { ...

 }

Bien sûr, dans un monde parfait, cela n'arriverait pas, mais ... eh bien ... parfois vous devez prendre en charge d'autres codes. :(


2
Cette méthode a des problèmes plus graves que la finale manquante. Il est assez rare, mais pas impossible, qu'il y ait une bonne raison pour qu'une méthode soit si encombrée que ce genre d'erreurs se produise. Un peu de réflexion sur les noms de variables contribuerait grandement à des accidents comme celui-ci.
ykaganovich

2
Si vous avez "des centaines de lignes de code" dans une seule méthode, vous voudrez peut-être la diviser en plusieurs méthodes plus petites.
Steve Kuo

5

Si vous écrivez une application que quelqu'un devra lire le code après, disons, 1 an, alors oui, utilisez final sur une variable qui ne devrait pas être modifiée tout le temps. En faisant cela, votre code sera plus «auto-documenté» et vous réduirez également la chance pour les autres développeurs de faire des choses stupides comme utiliser une constante locale comme variable temporaire locale.

Si vous écrivez du code jetable, alors, non, ne vous souciez pas d'identifier toutes les constantes et de les rendre définitives.


4

J'utiliserai final autant que possible. Cela signalera si vous modifiez involontairement le champ. J'ai également défini les paramètres de méthode sur final. Ce faisant, j'ai attrapé plusieurs bogues du code que j'ai pris en charge lorsqu'ils essaient de «définir» un paramètre en oubliant les passes Java par valeur.


2

La question de savoir si cela est évident n'est pas clair, mais rendre un paramètre de méthode final n'affecte que le corps de la méthode. Il ne transmet AUCUNE information intéressante sur les intentions de la méthode à l'invocateur. L'objet transmis peut toujours être muté dans la méthode (les finals ne sont pas des consts), et la portée de la variable est dans la méthode.

Pour répondre à votre question précise, je ne prendrais pas la peine de rendre une instance ou une variable locale (y compris les paramètres de méthode) finale à moins que le code ne l'exige (par exemple, la variable est référencée à partir d'une classe interne), ou pour clarifier une logique vraiment compliquée.

Par exemple, les variables, je les rendrais définitives si elles sont logiquement des constantes.


2

Il existe de nombreuses utilisations de la variable final. En voici quelques-uns

Constantes finales

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

Cela peut être utilisé pour d'autres parties de vos codes, ou accédé par d'autres classes, de cette façon si jamais vous changiez la valeur, vous n'auriez pas à les changer une par une.

Variables finales

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

  }

}

Dans cette classe, vous créez une variable finale de portée qui ajoute un préfixe au paramètre environmentKey. Dans ce cas, la variable finale n'est finale que dans le périmètre d'exécution, qui est différent à chaque exécution de la méthode. Chaque fois que la méthode est entrée, la finale est reconstruite. Dès qu'il est construit, il ne peut pas être modifié pendant la portée de l'exécution de la méthode. Cela vous permet de fixer une variable dans une méthode pour la durée de la méthode. voir ci-dessous:

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");
  }
}

Constantes finales

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   
}

Ceux-ci sont particulièrement utiles lorsque vous avez de très longues lignes de codes, et cela générera une erreur du compilateur afin que vous ne vous heurtiez pas à une erreur logique / commerciale lorsque quelqu'un change accidentellement des variables qui ne devraient pas être modifiées.

Collections finales

Cas différent lorsque nous parlons de collections, vous devez les définir comme non modifiables.

 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);
    }

sinon, si vous ne le définissez pas comme non modifiable:

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

Les classes finales et les méthodes finales ne peuvent pas être étendues ou écrasées respectivement.

MODIFIER: POUR RÉPONDRE AU PROBLÈME DE CLASSE FINALE CONCERNANT L'ENCAPSULATION:

Il y a deux façons de rendre une classe finale. La première consiste à utiliser le mot-clé final dans la déclaration de classe:

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

La deuxième façon de rendre une classe finale est de déclarer tous ses constructeurs comme privés:

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

Le marquer comme final vous évite les ennuis si vous découvrez qu'il s'agit réellement d'une finale, pour démontrer regardez cette classe de test. regarde public à première vue.

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

Malheureusement, comme le seul constructeur de la classe est privé, il est impossible d'étendre cette classe. Dans le cas de la classe Test, il n'y a aucune raison que la classe soit définitive. La classe Test est un bon exemple de la façon dont les classes finales implicites peuvent causer des problèmes.

Vous devez donc le marquer comme final lorsque vous rendez implicitement une classe finale en rendant son constructeur privé.


1

Un peu de compromis comme vous le mentionnez, mais je préfère l'utilisation explicite de quelque chose à l'utilisation implicite. Cela aidera à éliminer certaines ambiguïtés pour les futurs responsables du code - même si ce n'est que vous.


1

Si vous avez des classes internes (anonymes) et que la méthode a besoin d'accéder à la variable de la méthode contenant, vous devez avoir cette variable comme finale.

À part cela, ce que vous avez dit est juste.


Désormais, java 8 offre une flexibilité avec des variables finales efficaces.
Ravindra babu

0

Utilisez un finalmot-clé pour une variable si vous créez cette variable commeimmutable

En déclarant la variable comme finale, cela aide les développeurs à exclure d'éventuels problèmes de modification des variables dans un environnement hautement multithread.

Avec la version java 8, nous avons un autre concept appelé " effectively final variable". Une variable non finale peut devenir une variable finale.

les variables locales référencées à partir d'une expression lambda doivent être finales ou effectivement finales

Une variable est considérée comme définitive effective si elle n'est pas modifiée après l'initialisation dans le bloc local. Cela signifie que vous pouvez maintenant utiliser la variable locale sans mot-clé final dans une classe anonyme ou une expression lambda, à condition qu'elles soient effectivement définitives.

Jusqu'à Java 7, vous ne pouvez pas utiliser une variable locale non finale dans une classe anonyme, mais à partir de Java 8, vous pouvez

Jetez un œil à cet article


-1

Tout d'abord, le mot-clé final est utilisé pour rendre une variable constante. Constante signifie que cela ne change pas. Par exemple:

final int CM_PER_INCH = 2.54;

Vous déclareriez la variable finale car un centimètre par pouce ne change pas.

Si vous essayez de remplacer une valeur finale, la variable est ce qu'elle a été déclarée en premier. Par exemple:

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

Il y a une erreur de compilation qui est quelque chose comme:

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

Si votre variable ne peut pas être déclarée finale ou si vous ne voulez pas la déclarer définitive, essayez ceci:

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

Cela imprimera:

Hello World!
A String
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.