En Java, dois-je utiliser «final» pour les paramètres et les locales, même si ce n’est pas nécessaire?

105

Java permet de marquer les variables (champs / variables locales / paramètres) comme final, pour empêcher leur ré-affectation. Je trouve cela très utile avec les champs, car cela m'aide à voir rapidement si certains attributs - ou une classe entière - sont censés être immuables.

D'autre part, je le trouve beaucoup moins utile avec les paramètres locaux et les paramètres, et j'évite en général de les marquer comme finals'ils ne seraient jamais réaffectés (sauf exception évidente lorsqu'ils doivent être utilisés dans une classe interne). . Dernièrement, cependant, je suis tombé sur un code qui utilisait la version finale chaque fois que possible, ce qui, techniquement, fournit davantage d’informations.

N'ayant plus confiance en mon style de programmation, je me demande quels sont les autres avantages et inconvénients de l'application finaln'importe où, quel est le style le plus répandu dans l'industrie et pourquoi.

Chêne
la source
Les questions de style codage @Amir semblent appartenir ici mieux qu’à SO, et je n’ai trouvé aucune politique dans la FAQ ni dans la méta de ce site à ce sujet. Pouvez-vous s'il vous plaît me diriger?
Chêne
1
@Oak dit: "Problème de programmation spécifique, algorithmes logiciels, codage , interrogez Stack Overflow"
Amir Rezaei
7
@Amir je ne suis pas d'accord, je ne vois pas en quoi c'est un problème de codage. De toute façon, ce débat n’a pas sa place ici, j’ai donc ouvert un méta-sujet sur ce sujet .
Chêne
3
@amir c'est totalement sur le sujet de ce site, car c'est une question subjective sur la programmation - s'il vous plaît voir le / faq
Jeff Atwood
1
Variables locales: Les anciens compilateurs Java ont cherché à savoir si vous aviez déclaré un local finalou non et optimisé en conséquence. Les compilateurs modernes sont assez intelligents pour le découvrir par eux-mêmes. Au moins sur les variables locales, finalest strictement à l'avantage des lecteurs humains. Si vos routines ne sont pas trop compliquées, la plupart des lecteurs humains devraient également être en mesure de les comprendre eux-mêmes.
Salomon Slow

Réponses:

67

J'utilise finalla même manière que toi. Pour moi, cela semble superflu pour les variables locales et les paramètres de méthode, et cela ne fournit aucune information supplémentaire utile.

Une chose importante est que je m'efforce de garder mes méthodes courtes et propres , chacune effectuant une tâche unique. Ainsi, mes variables et paramètres locaux ont une portée très limitée et ne sont utilisés que dans un seul but. Cela minimise les chances de les réaffecter par inadvertance.

De plus, comme vous le savez sûrement, finalne garantit pas que vous ne pouvez pas changer la valeur / l’état d’une variable (non primitive). Seulement que vous ne pouvez pas réaffecter la référence à cet objet une fois initialisé. En d'autres termes, cela ne fonctionne de manière transparente qu'avec des variables de type primitif ou immuable. Considérer

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

Cela signifie que, dans de nombreux cas, cela ne permet toujours pas de comprendre plus facilement ce qui se passe dans le code, et une mauvaise compréhension peut en fait provoquer des bogues subtils difficiles à détecter.

Péter Török
la source
1
En ce qui concerne l'édition - je suis conscient de la sémantique de final, merci :) mais bon point au sujet des méthodes courtes et propres - je suppose que si la méthode est suffisamment courte pour qu'il soit évident qu'une variable ne soit pas réaffectée, il y a encore moins de motif pour considérer le finalmot clé.
Chêne
Ne serait-il pas formidable de disposer de paramètres finaux et de variables locales, tout en conservant une syntaxe courte et claire? programmers.stackexchange.com/questions/199783/…
oberlies
7
"final ne garantit pas que vous ne pouvez pas changer la valeur / l’état d’une variable (non primitive). Seulement, vous ne pouvez pas réaffecter la référence à cet objet une fois initialisée." Non primitive = référence (les seuls types en Java sont les types primitifs et les types de référence). La valeur d'une variable de type référence est une référence. Par conséquent, vous ne pouvez pas réaffecter la référence = vous ne pouvez pas modifier la valeur.
user102008
2
+1 pour le piège référence / état. Notez cependant que le marquage des variables final peut être nécessaire pour créer des fermetures dans les nouveaux aspects fonctionnels de Java8
Newtopian
Votre comparaison est biaisée, comme l'a souligné @ user102008. L'affectation de variable n'est pas la même chose que sa mise à jour de valeur
ericn
64

Votre style de programmation Java et vos pensées vont bien - vous n'avez pas besoin de douter de vous-même.

D'autre part, je le trouve beaucoup moins utile avec les paramètres locaux et les paramètres, et j'évite généralement de les marquer comme final, même s'ils ne seront jamais réaffectés (sauf exception évidente lorsqu'ils doivent être utilisés dans une classe interne. ).

C'est exactement pourquoi vous devriez utiliser le finalmot clé. Vous déclarez que VOUS savez qu'il ne sera jamais réaffecté, mais personne d'autre ne le sait. L'utilisation finaldésambiguise immédiatement votre code un tout petit peu plus.

Jonathan Khoo
la source
8
Si votre méthode est claire et ne fait qu’une chose, le lecteur le saura également. Le mot final rend seulement le code illisible. Et si son illisible est beaucoup plus ambiguë
eddieferetro
5
@eddieferetro En désaccord. Le mot clé finalindique l'intention, ce qui rend le code plus lisible. En outre, une fois que vous devez gérer le code du monde réel, vous remarquerez qu'il est rarement vierge et clair, et ajouter généreusement finals partout où vous le pourrez vous aidera à détecter les bogues et à mieux comprendre le code hérité.
Andres F.
4
"L'intention" est-elle que la variable ne changera jamais et que votre code s'appuie sur ce fait ? Ou bien l’intention est-elle "il se trouve que cette variable ne change jamais, alors je la marque comme finale". Ce dernier est un bruit inutile et éventuellement nuisible. Et puisque vous semblez préconiser de marquer toutes les sections locales, vous faites le dernier. Big -1.
user949300
2
finall'intention des états, qui est généralement une bonne chose dans un morceau de code, mais cela revient à ajouter de l'encombrement visuel. Comme la plupart des choses en programmation, il y a un compromis à faire ici: est-ce que le fait d'exprimer le fait que votre variable ne sera utilisée qu'une seule fois vaut la peine de la verbosité ajoutée?
christopheml
L'encombrement visuel sera considérablement réduit lors de l'utilisation de la coloration syntaxique. Je marque mes locaux qui sont attribués qu'une seule fois - et que je veux être assigné à une seule fois - avec final. C'est peut-être la plupart des habitants, mais clairement pas tous . Pour moi, il n'y a pas "n'arrive jamais à changer". Il existe 2 types d'habitants bien distincts dans mon monde. Ceux qui ne sont jamais supposés changer (final) et ceux qui le doivent à la suite de ce que la tâche à accomplir dicte (très peu, ce qui rend les fonctions assez faciles à raisonner).
blubberdiblub il y a
31

Un des avantages de final/ constlorsque cela est possible est que cela réduit la charge mentale du lecteur de votre code.

Il peut être assuré que la valeur / référence n’est jamais modifiée ultérieurement. Il n'a donc pas besoin de faire attention aux modifications pour comprendre le calcul.

J'ai changé d'avis à ce sujet après avoir appris les langages de programmation purement fonctionnels. Bon Dieu, quel soulagement, si vous pouvez faire confiance à une "variable" pour toujours conserver sa valeur initiale.

LennyProgrammers
la source
16
Eh bien, Java finalne garantit pas que vous ne pouvez pas changer la valeur / l’état d’une variable (non primitive). Seulement que vous ne pouvez pas réaffecter la référence à cet objet une fois initialisé.
Péter Török le
9
Je sais, c'est pourquoi j'ai distingué entre valeur et référence. Le concept est le plus utile dans le contexte de structures de données immuables et / ou de fonctionnalités pures.
LennyProgrammers le
7
@ PéterTörök C'est immédiatement utile avec les types primitifs, et un peu utile avec les références à des objets mutables (au moins, vous savez que vous utilisez toujours le même objet!). C'est extrêmement utile lorsqu'il s'agit de code conçu pour être immuable de fond en comble.
Andres F.
18

Je considère finaldans les paramètres de méthode et les variables locales le code de bruit. Les déclarations de méthodes Java peuvent être assez longues (surtout avec les génériques) - il n'est pas nécessaire de les créer plus longtemps.

Si les tests unitaires sont écrits correctement, l’attribution de paramètres «nocifs» sera prise en compte, il ne devrait donc jamais y avoir de problème. La clarté visuelle est plus importante que d'éviter un bug possible qui n'est pas détecté car la couverture de vos tests unitaires est insuffisante.

Des outils tels que FindBugs et CheckStyle peuvent être configurés pour interrompre la construction si une affectation est faite à des paramètres ou à des variables locales, si de telles choses vous tiennent à coeur.

Bien sûr, si vous devez les rendre finales, par exemple parce que vous utilisez la valeur dans une classe anonyme, alors pas de problème, c'est la solution la plus simple et la plus propre.

Outre l'effet évident d'ajouter des mots-clés supplémentaires à vos paramètres et de les camoufler ainsi à mon humble avis, l'ajout des paramètres de méthode finaux peut souvent rendre le code du corps de la méthode moins lisible, ce qui aggrave le code - d'être "bon", code doit être aussi lisible et aussi simple que possible. Pour un exemple artificiel, disons que j'ai une méthode qui doit fonctionner sans distinction de casse.

Sans final:

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

Facile. Nettoyer. Tout le monde sait ce qui se passe.

Maintenant avec 'final', option 1:

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

Bien que définir les paramètres finalempêche le codeur d’ajouter du code parce qu’il pense ne pas travailler avec la valeur originale, il existe un risque équivalent que le code plus bas puisse utiliser à la inputplace lowercaseInput, ce qu’il ne devrait pas et contre lequel on ne peut pas se protéger, car vous pouvez " t le sortir de la portée (ou même assigner nullà inputsi cela pourrait même aider de toute façon).

Avec 'final', option 2:

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

Nous venons tout juste de créer encore plus de bruit de code et d’avoir à invoquer toLowerCase()n fois.

Avec 'final', option 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
}

Parlez du bruit de code. Ceci est une épave de train. Nous avons une nouvelle méthode, un bloc d'exception requis, car un autre code peut ne pas l'appeler correctement. Plus de tests unitaires pour couvrir l'exception. Tout pour éviter une ligne simple, et à mon humble avis préférable et sans danger,.

Il y a également le problème que les méthodes ne devraient pas être si longues que vous ne pouvez pas facilement les assimiler visuellement et savoir en un coup d'œil qu'une affectation à un paramètre a eu lieu.

Je pense que c’est une bonne pratique / style que, si vous affectez un paramètre, vous le fassiez au début de la méthode, de préférence en première ligne ou juste après la vérification de base des entrées, en le remplaçant efficacement pour toute la méthode, ce qui a un effet cohérent au sein de la méthode. méthode. Les lecteurs savent qu’ils s’attendent à ce que toute tâche soit évidente (à proximité de la déclaration de signature) et dans un endroit cohérent, ce qui atténue considérablement le problème que l’ajout de final tente d’éviter. En fait, j'assigne rarement des paramètres, mais si je le fais, je le fais toujours au sommet d'une méthode.


Notez également que finalne vous protège pas réellement comme cela peut paraître au premier abord:

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

final ne vous protège pas complètement sauf si le type de paramètre est primitif ou immuable.

bohémien
la source
Dans le dernier cas, finalfournit la garantie partielle que vous traitez avec la même Dateinstance. Certaines garanties valent mieux que rien (si tant est que vous plaidiez pour des classes immuables à partir de rien!). Quoi qu'il en soit, dans la pratique, une grande partie de ce que vous dites devrait affecter les langues existantes immuables par défaut, mais ce n'est pas le cas, ce qui signifie que ce n'est pas un problème.
Andres F.
+1 pour indiquer le risque "ce code plus bas peut utiliser l'entrée". Néanmoins, je préférerais que mes paramètres soient final, avec une possibilité de les rendre non définitifs pour les cas comme ci-dessus. Il est inutile de spammer la signature avec un mot clé.
Maaartinus
Je trouve le point que l'on peut utiliser par erreur inputau lieu de lowercaseInputthéorique. Bien que techniquement correct, il est facile de repérer une erreur une fois que cela vous cause un bogue, car vous pouvez clairement voir les différentes sémantiques des 2 variables nommées séparément à leur point d'utilisation (à condition de leur donner de bons noms). Pour moi, il est plus dangereux de confondre 2 utilisations sémantiquement différentes de valeurs séparées dans la même variable et donc le même nom. Cela peut rendre plus difficile le traçage des bogues, car vous devrez lire et comprendre tout le code précédent qui est en quelque sorte lié à la variable 1.
blubberdiblub
7

Je laisse Eclipse mettre finalavant chaque variable locale, car j’estime que cela facilitera la lecture du programme. Je ne le fais pas avec des paramètres, car je veux garder la liste de paramètres aussi courte que possible, idéalement, elle devrait tenir dans une ligne.

maaartinus
la source
2
En outre, pour les paramètres, vous pouvez demander au compilateur d'émettre un avertissement ou une erreur si un paramètre est attribué.
oberlies
3
Bien au contraire, j'ai du mal à lire car cela encombre le code.
Steve Kuo
3
@ Steve Kuo: Cela me permet simplement de trouver rapidement toutes les variables. pas de gros gain, mais avec la prévention des affectations accidentelles, cela vaut les 6 caractères. Je serais beaucoup plus heureux s'il y avait quelque chose comme varpour marquer les variables non finales. YMMV.
Maaartinus
1
@maaartinus D'accord var! Malheureusement, ce n'est pas le cas en Java et il est maintenant trop tard pour changer de langue. Par conséquent, je suis prêt à supporter le désagrément mineur de l'écriture final:)
Andres F.
1
@ Maaartinus D'accord. Je trouve qu'environ 80 à 90% de mes variables (locales) n'ont pas besoin d'être modifiées après l'initialisation. Ainsi, 80 à 90% d'entre eux ont déjà finalajouté le mot clé, alors que seulement 10 à 20% auraient besoin d'un var...
JimmyB