web-dev-qa-db-fra.com

Pourquoi déclarer des variables finales à l'intérieur des méthodes?

En étudiant certaines classes d'Android, j'ai réalisé que la plupart des variables de méthodes sont déclarées définitives.

Exemple de code extrait de la classe Android.widget.ListView:

/**
* @return Whether the list needs to show the top fading Edge
*/
private boolean showingTopFadingEdge() {
    final int listTop = mScrollY + mListPadding.top;
    return (mFirstPosition > 0) || (getChildAt(0).getTop() > listTop);
}

/**
 * @return Whether the list needs to show the bottom fading Edge
 */
private boolean showingBottomFadingEdge() {
    final int childCount = getChildCount();
    final int bottomOfBottomChild = getChildAt(childCount - 1).getBottom();
    final int lastVisiblePosition = mFirstPosition + childCount - 1;
    final int listBottom = mScrollY + getHeight() - mListPadding.bottom;
    return (lastVisiblePosition < mItemCount - 1)
                     || (bottomOfBottomChild < listBottom);
}

Quelle est l'intention d'utiliser le mot-clé final dans ces cas?

76
Rodrigo

Je dirais que cela est dû à force d'habitude. Le programmeur qui a écrit ce code savait pendant qu'il l'écrivait que les valeurs des variables finales ne devraient jamais être modifiées après l'affectation, et les a donc rendues définitives. Toute tentative d'attribution d'une nouvelle valeur à une variable finale après l'affectation entraînera une erreur de compilation.

Au fil des habitudes, ce n'est pas une mauvaise chose à développer. Au moins, faire une variable finale spécifie le intention du programmeur au moment de l'écriture. Ceci est important car cela pourrait donner aux programmeurs ultérieurs qui modifient le code une pause de réflexion avant de commencer à changer la façon dont cette variable est utilisée.

64
Jon

Parlant en tant que Java qui crée toutes les variables final par défaut (et qui apprécie le fait qu'Eclipse puisse le faire automatiquement), je trouve plus facile de raisonner sur mon programme si les variables sont initialisées une fois et ne sont plus jamais modifiées.

D'une part, les variables non initialisées ne sont plus un problème, car essayer d'utiliser une variable final avant qu'elle ne soit initialisée entraînera une erreur de compilation. Ceci est particulièrement utile pour la logique conditionnelle imbriquée, où je veux m'assurer que j'ai couvert tous les cas:

final int result;
if (/* something */) {
  if (/* something else */) {
    result = 1;
  }
  else if (/* some other thing */) {
    result = 2;
  }
}
else {
  result = 3;
}
System.out.println(result);

Ai-je couvert tous les cas? (Indice: Non.) Bien sûr, ce code ne compilera même pas.

Une dernière chose: en général, chaque fois que vous savez que quelque chose est toujours vrai à propos d'une variable, vous devez essayer d'obtenir votre langage pour le faire respecter. Nous le faisons tous les jours lorsque nous spécifions le type d'une variable, bien sûr: le langage garantira que les valeurs qui ne sont pas de ce type ne peuvent pas être stockées dans cette variable. De même, si vous savez qu'une variable ne doit pas être réaffectée car elle a déjà la valeur qu'elle doit conserver pour toute la méthode, vous pouvez obtenir le langage pour appliquer cette restriction en la déclarant final.

Enfin, il y a la question de l'habitude. D'autres ont mentionné qu'il s'agit d'une habitude (+1 à Jon pour cela ), mais permettez-moi de dire pourquoi vous voudriez cette habitude. Si vous déclarez des champs dans votre classe et non des variables locales dans une méthode, il est possible que plusieurs threads accèdent à ces champs en même temps. Il y a quelques exceptions obscures, mais en général, si un champ est final, alors chaque thread qui utilise votre classe verra la même valeur pour la variable. Inversement, si un champ n'est pas final et que plusieurs threads utilisent votre classe, vous devrez vous soucier de la synchronisation explicite à l'aide des blocs synchronized et/ou des classes de Java.util.concurrent. La synchronisation est possible, mais la programmation est déjà assez difficile. ;-) Donc, si vous déclarez simplement toutfinal par habitude, alors beaucoup de vos champs seront finaux et vous passerez le moins de temps possible à vous soucier de la synchronisation et les bogues liés à la concurrence.

Pour en savoir plus sur cette habitude, consultez l'astuce "Minimize Mutability" dans Joshua Bloch Effective Java .

Edit: @Peter Taylor a souligné que l'exemple ci-dessus ne serait pas non plus compilé si le mot clé final était supprimé, ce qui est tout à fait correct . Quand j'ai conseillé de garder toutes les variables locales finales, c'est parce que je voulais rendre impossible des exemples comme le suivant:

int result = 0;

// OK, time to cover all the cases!
if (/* something */) {
  if (/* something else */) {
    result = 1;
  }
  else if (/* some other thing */) {
    result = 2;
  }
  // Whoops, missed an "else" here. Too bad.
}
else {
  result = 3;
}
System.out.println(result);  // Works fine!

L'utilisation d'une nouvelle variable au lieu de réutiliser une ancienne est la façon dont je peux dire au compilateur qu'essayer de couvrir l'univers complet des possibilités, et utiliser les variables final m'oblige à utiliser une nouvelle variable au lieu de recycler une ancienne.

Une autre plainte valable concernant cet exemple est que vous devez éviter la logique conditionnelle imbriquée complexe en premier lieu. C'est vrai, bien sûr, précisément parce qu'il est difficile de s'assurer que vous avez couvert tous les cas de la manière que vous vouliez. Cependant, une logique parfois complexe ne peut être évitée. Lorsque ma logique est complexe, je veux que mes variables soient aussi simples à raisonner que possible, ce que je peux réaliser en m'assurant que les valeurs de mes variables ne changent jamais après leur initialisation.

53
Joe Carnahan

Nous ne pouvons pas connaître la réponse avec certitude (sauf si nous demandons aux développeurs d'origine), mais mes suppositions sont:

  1. Les programmeurs de ces méthodes ont peut-être estimé que l'ajout de "final" exprime mieux le but de ces variables.
  2. Les programmeurs peuvent utiliser un outil qui ajoute automatiquement "final" là où il le peut.
  3. Cela pourrait être une astuce d'optimisation: je n'ai pas les connaissances/outils pour le vérifier moi-même, mais je serais intéressé de savoir de toute façon.
7
Kramii

Une autre raison est de pouvoir les utiliser dans des classes anonymes internes:

public interface MyInterface {
    void foo();
} 

void myMethod() {
    final String asdf = "tada";//if not final cannot be used in inner classes
    new MyInterface() {
        @Override
        public void foo() {
             String myNewString = asdf;//does not compile only if asdf is final
        }
    }
}
6
m3th0dman

Pour faire des erreurs idiotes moins fréquentes. final s'assure que la variable pointe toujours vers son premier objet assigné, et toute modification tentée comptera comme une erreur au moment de la compilation.

Vous pouvez également demander "pourquoi déclarer explicitement le type d'une variable?", Ou "pourquoi déclarer des méthodes constantes en C++?". Même raison.

4
Yam Marcovic

Voici la raison pour laquelle: Éviter la réaffectation aux variables/paramètres locaux. Cela augmenterait la lisibilité et éviterait quelques bugs stupides.

http://sourcemaking.com/refactoring/remove-assignments-to-parameters

Extrait:

Java 1.1 et les versions ultérieures vous permettent de marquer un paramètre comme final; cela empêche l'affectation à la variable. Il vous permet toujours de modifier l'objet auquel la variable fait référence. Je traite toujours mes paramètres comme définitifs, mais j'avoue que je les marque rarement dans la liste des paramètres.

2
Mik378

Honnêtement, je ne pense pas qu'il y aurait une vraie raison de rendre une variable finale dans ce contexte. Je suppose qu'ils copient et collent le code, ou ils veulent vraiment décourager quiconque ajoute plus de code à cette méthode de réaffecter ces variables.

0
programmx10