web-dev-qa-db-fra.com

Pourquoi devrais-je utiliser le mot clé "final" sur un paramètre de méthode en Java?

Je ne comprends pas où est le mot-clé finalvraiment quand il est utilisé dans les paramètres de méthode.

Si nous excluons l’utilisation de classes anonymes, la lisibilité et la déclaration d’intention, cela me semble presque inutile.

Faire en sorte que certaines données restent constantes n’est pas aussi fort qu’il semble.

  • Si le paramètre est une primitive, il n'aura aucun effet car le paramètre est transmis à la méthode en tant que valeur et sa modification n'aura aucun effet en dehors de la portée.

  • Si nous transmettons un paramètre par référence, la référence elle-même est une variable locale et si la référence est modifiée depuis la méthode, cela n'aura aucun effet en dehors de la portée de la méthode.

Prenons l'exemple de test simple ci-dessous. Ce test réussit bien que la méthode ait changé la valeur de la référence qui lui a été donnée, cela n’a aucun effet.

public void testNullify() {
    Collection<Integer> c  = new ArrayList<Integer>();      
    nullify(c);
    assertNotNull(c);       
    final Collection<Integer> c1 = c;
    assertTrue(c1.equals(c));
    change(c);
    assertTrue(c1.equals(c));
}

private void change(Collection<Integer> c) {
    c = new ArrayList<Integer>();
}

public void nullify(Collection<?> t) {
    t = null;
}
362
Yotam Gilboa

Parfois, il est bien d’être explicite (pour la lisibilité) que la variable ne change pas. Voici un exemple simple où utiliser final peut économiser certains maux de tête possibles:

public void setTest(String test) {
    test = test;
}

Si vous oubliez le mot-clé 'this' sur un setter, la variable que vous souhaitez définir ne sera pas définie. Toutefois, si vous utilisiez le mot clé final dans le paramètre, le bogue serait intercepté au moment de la compilation.

225
Jerry Sha

Arrêter la réaffectation d’une variable

Bien que ces réponses soient intellectuellement intéressantes, je n'ai pas lu la réponse courte et simple:

Utilisez le mot-clé final lorsque vous voulez que le compilateur empêche une variable d'être réaffectée à un autre objet.

Que la variable soit une variable statique, une variable membre, une variable locale ou une variable argument/paramètre, l'effet est totalement identique.

Exemple

Voyons l’effet en action.

Considérez cette méthode simple, où les deux variables (arg et x) peuvent être affectées à différents objets.

// Example use of this method: 
//   this.doSomething( "tiger" );
void doSomething( String arg ) {
  String x = arg;   // Both variables now point to the same String object.
  x = "elephant";   // This variable now points to a different String object.
  arg = "giraffe";  // Ditto. Now neither variable points to the original passed String.
}

Marquez la variable locale comme final. Cela entraîne une erreur du compilateur.

void doSomething( String arg ) {
  final String x = arg;  // Mark variable as 'final'.
  x = "elephant";  // Compiler error: The final local variable x cannot be assigned. 
  arg = "giraffe";  
}

A la place, marquons la variable de paramètre comme final. Cela entraîne également une erreur du compilateur.

void doSomething( final String arg ) {  // Mark argument as 'final'.
  String x = arg;   
  x = "elephant"; 
  arg = "giraffe";  // Compiler error: The passed argument variable arg cannot be re-assigned to another object.
}

Morale de l'histoire:

Si vous voulez vous assurer qu'une variable pointe toujours sur le même objet, marquez la variable final.

Ne jamais réaffecter d'arguments

Comme bonne pratique de programmation (dans n’importe quel langage), vous devez jamais réaffecter une variable paramètre/argument à un objet autre que celui transmis par la méthode d’appel. Dans les exemples ci-dessus, il ne faut jamais écrire la ligne arg =. Puisque les humains font des erreurs et que les programmeurs sont humains, demandons au compilateur de nous aider. Marquez chaque variable paramètre/argument comme "finale" afin que le compilateur puisse trouver et marquer de telles ré-assignations.

En rétrospective

Comme indiqué dans d'autres réponses… Etant donné l'objectif initial de conception de Java consistant à aider les programmeurs à éviter les erreurs stupides telles que la lecture après la fin d'un tableau, Java aurait dû être conçu pour appliquer automatiquement toutes les variables paramètre/argument comme "final". '. En d'autres termes, Les arguments ne doivent pas être des variables. Mais le recul est une vision 20/20, et les Java concepteurs avaient les mains pleines à l’époque.

Un autre cas ajouté pour la complétude

public class MyClass {
    private int x;
    //getters and setters
}

void doSomething( final MyClass arg ) {  // Mark argument as 'final'.

   arg =  new MyClass();  // Compiler error: The passed argument variable arg  cannot be re-assigned to another object.

   arg.setX(20); // allowed
  // We can re-assign properties of argument which is marked as final
 }
216
Basil Bourque

Oui, à l'exclusion des classes anonymes, de la lisibilité et de la déclaration d'intention, cela ne vaut presque rien. Ces trois choses sont-elles sans valeur cependant?

Personnellement, j'ai tendance à ne pas utiliser final pour les variables locales et les paramètres, sauf si j'utilise la variable dans une classe interne anonyme, mais je peux certainement voir l'intérêt de ceux qui veulent préciser que la valeur du paramètre a elle-même gagné 'change pas (même si l'objet auquel il se réfère change de contenu). Pour ceux qui trouvent que cela ajoute à la lisibilité, je pense que c'est une chose tout à fait raisonnable à faire.

Votre remarque serait plus importante si quelqu'un prétendait réellement que l'a fait maintenait les données constantes d'une manière qui ne le ferait pas - mais je ne me souviens pas avoir vu de telles affirmations. Êtes-vous en train de suggérer qu’un grand nombre de développeurs suggèrent que final a plus d’effet qu’elle ne le fait réellement?

EDIT: J'aurais vraiment dû résumer tout cela avec une référence Monty Python; la question semble un peu similaire à celle de demander "Qu'est-ce que les Romains ont jamais fait pour nous?"

126
Jon Skeet

Laissez-moi vous expliquer un peu le cas où vous devez utiliser final, ce que Jon a déjà mentionné:

Si vous créez une classe interne anonyme dans votre méthode et utilisez une variable locale (telle qu'un paramètre de méthode) à l'intérieur de cette classe, le compilateur vous oblige à rendre le paramètre final:

public Iterator<Integer> createIntegerIterator(final int from, final int to)
{
    return new Iterator<Integer>(){
        int index = from;
        public Integer next()
        {
            return index++;
        }
        public boolean hasNext()
        {
            return index <= to;
        }
        // remove method omitted
    };
}

Ici, les paramètres from et to doivent être définitifs pour pouvoir être utilisés dans la classe anonyme.

La raison de cette exigence est la suivante: les variables locales résident dans la pile et n'existent donc que pendant l'exécution de la méthode. Toutefois, l'instance de classe anonyme est renvoyée à partir de la méthode, de sorte qu'elle peut durer beaucoup plus longtemps. Vous ne pouvez pas conserver la pile, car elle est nécessaire pour les appels de méthode suivants.

Donc, ce que Java fait à la place, c'est de mettre des copies de ces variables locales en tant que variables d'instance masquées dans la classe anonyme (vous pouvez les voir si vous examinez le code d'octet). Mais si elles ne sont pas finales, on peut s’attendre à ce que la classe anonyme et la méthode qui est vue changent l’autre à la variable. Afin de maintenir l'illusion qu'il n'y a qu'une seule variable plutôt que deux copies, elle doit être finale.

75
Michael Borgwardt

J'utilise final tout le temps sur les paramètres.

Cela ajoute-t-il beaucoup? Pas vraiment.

Est-ce que je l'éteindrais? Non.

La raison: j'ai trouvé 3 bugs où les gens avaient écrit du code bâclé et n'avaient pas réussi à définir une variable membre dans les accesseurs. Tous les bugs se sont révélés difficiles à trouver.

J'aimerais que cela devienne la valeur par défaut dans une future version de Java. Le principe de passage par valeur/référence attire énormément de jeunes programmeurs.

Une dernière chose .. mes méthodes ont tendance à avoir un petit nombre de paramètres donc le texte supplémentaire sur une déclaration de méthode n'est pas un problème.

25
Fortyrunner

L'utilisation de final dans un paramètre de méthode n'a rien à voir avec le sort de l'argument côté appelant. Il est uniquement destiné à le marquer comme ne changeant pas à l'intérieur de cette méthode. En essayant d’adopter un style de programmation plus fonctionnel, j’en vois la valeur.

18
Germán

Personnellement, je n’utilise pas les paramètres de méthode final, car cela ajoute trop de fouillis aux listes de paramètres. Je préfère appliquer que les paramètres de méthode ne soient pas modifiés par quelque chose comme Checkstyle.

Pour les variables locales que j'utilise final lorsque cela est possible, je laisse même Eclipse le faire automatiquement dans ma configuration pour les projets personnels.

Je voudrais certainement quelque chose de plus fort comme C/C++ const.

8
starblue

Puisque Java passe des copies d’arguments, j’estime que sa pertinence est plutôt limitée. J'imagine que cela vient de l'ère C++ où l'on pouvait interdire le contenu de référence à être modifié en faisant un const char const *. Je pense que ce genre de choses vous fait croire que le développeur est stupide en soi et doit être protégé contre tous les personnages qu'il tape. En toute humilité, dois-je dire que j’écris très peu de bugs, même si j’ai déclaré la final à moins que je ne veuille pas que l’on déroge à mes méthodes et à tout ce que je fais… Peut-être que je ne suis qu’un vieux devin. ..: -O

4
Lawrence

Réponse courte: final aide un tout petit peu mais ... utilise plutôt une programmation défensive côté client.

En effet, le problème avec final est qu’il n’applique que référence est inchangé, permettant ainsi aux membres de l’objet référencé d’être mutés, à l’insu de l’appelant. Par conséquent, la meilleure pratique à cet égard est la programmation défensive du côté appelant, qui crée des instances profondément immuables ou des copies profondes d’objets risquant d’être volés par des API peu scrupuleuses.

1
Darrell Teague

Je n'utilise jamais final dans une liste de paramètres, cela ajoute simplement un fouillis, comme l'ont dit les répondants précédents. Éclipse vous permet également de définir le paramétrage afin de générer une erreur. Ainsi, utiliser final dans une liste de paramètres me semble plutôt redondant. Fait intéressant, lorsque j'ai activé le paramètre Eclipse pour le paramétrage, générer une erreur a intercepté ce code (voici comment je me souviens du flux, pas du code lui-même.): -

private String getString(String A, int i, String B, String C)
{
    if (i > 0)
        A += B;

    if (i > 100)
        A += C;

    return A;
}

En jouant à l'avocat du diable, qu'est-ce qui ne va pas avec cela?

1
Chris Milburn

Une autre raison d'ajouter la dernière aux déclarations de paramètre est qu'il est utile d'identifier les variables qui doivent être renommées dans le cadre d'un refactoring "Méthode d'extraction". J'ai constaté que l'ajout de final à chaque paramètre avant de commencer une refactorisation de méthode de grande taille m'indique rapidement s'il y a des problèmes que je dois résoudre avant de continuer.

Cependant, je les supprime généralement comme superflus à la fin du refactoring.

0
Michael Rutherfurd