web-dev-qa-db-fra.com

En Java, dois-je utiliser "final" pour les paramètres et les paramètres locaux même si je n'en ai pas besoin?

Java permet de marquer les variables (champs/paramètres locaux/paramètres) comme final, pour éviter de les réattribuer. 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'un autre côté, je le trouve beaucoup moins utile avec les paramètres locaux et les paramètres, et généralement j'évite de les marquer comme final même s'ils ne seront jamais réaffectés dans (à l'exception évidente quand ils doivent être utilisé dans une classe interne). Dernièrement, cependant, je suis tombé sur du code qui utilisait final dans la mesure du possible, ce qui, je suppose, fournit techniquement plus 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 de final n'importe où, quel est le style le plus courant de l'industrie et pourquoi.

114
Oak

J'utilise final de la même manière que vous. Pour moi, cela semble superflu sur les variables locales et les paramètres de méthode, et il ne transmet pas d'informations supplémentaires utiles.

Une chose importante est que nous nous efforçons de garder mes méthodes courtes et propres, chacun faisant une seule tâche. 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, 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é. En d'autres termes, il ne fonctionne de manière transparente qu'avec des variables de types primitifs ou immuables. 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 facilite toujours pas la compréhension de ce qui se passe à l'intérieur du code, et une mauvaise compréhension peut en fait entraîner des bogues subtils difficiles à détecter.

68
Péter Török

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

D'un autre côté, je le trouve beaucoup moins utile avec les paramètres locaux et les paramètres, et généralement j'évite de les marquer comme définitifs même s'ils ne seront jamais réassignés (à l'exception évidente lorsqu'ils doivent être utilisés dans une classe interne) ).

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

66
J.K.

Un avantage d'utiliser final/const dans la mesure du possible est qu'il réduit la charge mentale pour le lecteur de votre code.

Il peut être assuré que la valeur/référence n'est jamais modifiée par la suite. 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 des langages de programmation purement fonctionnels. Boy, quel soulagement, si vous pouvez faire confiance à une "variable" pour toujours conserver sa valeur initiale.

32
LennyProgrammers

Je considère final dans les paramètres de méthode et les variables locales comme du bruit de code. Java peuvent être assez longues (en particulier avec les génériques) - il n'est pas nécessaire de les prolonger.

Si les tests unitaires sont écrits correctement, l'attribution à des paramètres "nuisibles" sera récupérée, donc cela ne devrait jamais en fait être un problème. La clarté visuelle est plus importante que d'éviter un bogue possible qui n'est pas détecté car vos tests unitaires ont une couverture insuffisante.

Des outils comme FindBugs et CheckStyle qui peuvent être configurés pour interrompre la construction si l'affectation est faite à des paramètres ou à des variables locales, si vous vous souciez profondément de ces choses.

Bien sûr, si vous besoin pour les rendre définitifs, 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 de l'ajout de mots clés supplémentaires à vos paramètres, et donc de leur camouflage à mon humble avis, l'ajout de paramètres finaux aux méthodes peut souvent rendre le code dans le corps de la méthode moins lisible, ce qui aggrave le code - pour être "bon", le code doit être aussi lisible et aussi simple que possible. Pour un exemple artificiel, disons que j'ai une méthode qui doit fonctionner de manière insensible.

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
}

Tout en définissant les paramètres final empêche le codeur d'ajouter du code plus loin de penser qu'il travaille avec la valeur d'origine, il y a un risque égal que le code plus bas utilise input au lieu de lowercaseInput , ce qu'il ne devrait pas et contre lequel ne peut pas être protégé, car vous ne pouvez pas le retirer de la portée (ou même affecter null à input si cela peut même aider de toute façon).

Avec 'final', option 2:

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

Maintenant, nous venons de créer encore plus de bruit de code et avons introduit un impact sur les performances d'avoir à appeler 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 du code. Ceci est une épave de train. Nous avons une nouvelle méthode, un bloc d'exception requis, car un autre code peut l'appeler de manière incorrecte. Plus de tests unitaires pour couvrir l'exception. Tout pour éviter une ligne simple, et à mon humble avis préférable et inoffensive.

Il y a aussi le problème que les méthodes ne devraient pas être si longues que vous ne pouvez pas facilement les intégrer visuellement et savoir d'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 attribuez à un paramètre, vous le faites tous les début de la méthode, de préférence la première ligne ou tout de suite après la vérification des entrées de base, en fait remplacement pour le entier méthode, qui a un effet cohérent au sein de la méthode. Les lecteurs savent que toute affectation doit être évidente (près de la déclaration de signature) et cohérente, ce qui atténue considérablement le problème que l'ajout de final tente d'éviter. En fait, j'attribue rarement des paramètres, mais si je le fais, je le fais toujours en haut d'une méthode.


Notez également que final ne vous protège pas réellement comme cela peut sembler à première vue:

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.

22
Bohemian

Je laisse Eclipse mettre final avant chaque variable locale, car je considère que cela facilite la lecture du programme. Je ne le fais pas avec des paramètres, car je veux garder la liste des paramètres aussi courte que possible, idéalement, elle devrait tenir sur une seule ligne.

7
maaartinus