Le mot-clé final en Java
Rendez des variables constantes, empêchez la surcharge de méthodes et interdisez l'héritage de classes avec le mot-clé final en Java.
final signifie « définir ceci une fois, puis ne plus le modifier. » Il s'applique à quatre choses, et l'effet dépend de laquelle :
- Sur une variable, la liaison ne peut pas être réassignée.
- Sur une méthode, la méthode ne peut pas être surchargée par une sous-classe.
- Sur une classe, la classe ne peut pas être étendue.
- Sur un paramètre, le paramètre ne peut pas être réassigné à l'intérieur de la méthode.
Le fil conducteur est « aucune modification ultérieure après la liaison initiale. » Ce qui est exactement interdit dépend de ce qui est marqué.
Variables locales final
La forme la plus simple : une variable locale final ne peut pas être réassignée après sa première affectation.
void demo() {
final int max = 100;
max = 50; // ERROR: cannot assign a value to final variable max
}Il n'est pas obligatoire de l'initialiser à la déclaration — la première affectation peut être différée, tant qu'elle est effectuée exactement une fois avant toute lecture :
final int x;
if (condition) x = 1;
else x = 2;
System.out.println(x); // ok — x was definitely assignedfinal sur une variable locale est surtout un outil de documentation — il indique à un lecteur futur (et au compilateur) « cela ne changera plus après ce point. » Les captures dans les lambdas et les classes internes l'exigent : toute variable locale utilisée à l'intérieur doit être final ou effectivement finale (jamais réassignée).
Paramètres final
Un paramètre final ne peut pas être réassigné à l'intérieur du corps de la méthode :
void greet(final String name) {
name = name.toUpperCase(); // ERROR
}Certaines équipes exigent final sur chaque paramètre ; d'autres le trouvent visuellement encombrant. Les deux approches sont valables — ce qui compte, c'est la cohérence au sein d'une base de code. L'habitude de muter les paramètres que cela décourage est une vraie source de bugs.
Champs final
Un champ final est affecté exactement une fois — soit à la déclaration, dans un initialiseur d'instance, ou dans le constructeur — et jamais à nouveau :
public class Point {
private final int x, y;
public Point(int x, int y) {
this.x = x; // assigned once
this.y = y;
}
// no setX or setY — there's no way to change the values
}C'est le cœur d'une classe immuable. Chaque champ est final ; rien ne peut muter l'objet après sa construction.
Le compilateur vérifie que chaque champ final est affecté exactement une fois sur chaque chemin de constructeur. Manquer un chemin provoque une erreur de compilation. Affecter deux fois provoque également une erreur de compilation.
static final — constantes
La forme standard pour une constante est public static final suivi d'un nom en UPPER_SNAKE :
public static final double PI = 3.141592653589793;
public static final int MAX_RETRY = 3;Static, parce qu'il n'y a pas de raison de conserver une copie par instance ; final, parce que c'est une constante. Pour les constantes primitives et String, le compilateur intègre la valeur à chaque site d'appel. Une conséquence : si vous modifiez la valeur d'une telle constante, les classes qui y font référence conservent l'ancienne valeur jusqu'à leur recompilation — le littéral a été intégré dans leur bytecode au moment de leur propre compilation.
final et les références
Un malentendu courant : final gèle la référence, pas l'objet vers lequel elle pointe. Avec un tableau ou une liste final, vous pouvez toujours muter le contenu :
final int[] nums = {1, 2, 3};
nums[0] = 99; // ok — mutating the array, not reassigning the reference
nums = new int[5]; // ERROR — reassigning the reference
final List<String> names = new ArrayList<>();
names.add("Rex"); // ok — mutates the list
names = List.of(); // ERRORSi vous voulez une liste dont le contenu est également figé, encapsulez-la avec List.copyOf(...) ou Collections.unmodifiableList(...).
Méthodes final
final sur une méthode signifie qu'aucune sous-classe ne peut la surcharger :
public class Shape {
public final String describe() { // can never be overridden
return getClass().getSimpleName() + " area=" + area();
}
public double area() { return 0; }
}
public class Circle extends Shape {
public String describe() { return "circle"; } // ERROR
}Vous marquez une méthode final lorsque sa surcharge briserait des invariants dont la classe dépend. C'est un choix de conception avisé — la plupart des méthodes ne sont pas final dans les bases de code réelles — mais pour les gabarits qui ne doivent pas changer, c'est l'outil approprié.
Classes final
final sur une classe signifie qu'aucune classe ne peut l'étendre :
public final class Money { ... }
public class CryptoMoney extends Money { } // ERRORUtilisez ceci lorsque l'extension compromettrait la conception. La bibliothèque standard le fait régulièrement : String, Integer, Long, Double, et le reste des wrappers primitifs sont tous final — les étendre permettrait d'introduire un comportement mutable dans un type que le langage traite comme immuable.
final n'est pas l'immuabilité
Un champ final seul ne rend pas un objet immuable ; il empêche simplement la référence de changer. La vraie immuabilité requiert :
- Chaque champ
final. - La classe elle-même
final, ou construite avec soin pour empêcher les sous-classes d'ajouter un état mutable. - Aucune méthode qui expose un objet interne mutable (copies défensives en sortie).
- Aucune méthode permettant à un appelant de muter l'état à travers elle.
Les records (couverts dans records) regroupent la plupart de cela automatiquement.
Un exemple concret
Prochaine étape
Les champs private et l'immuabilité avec final sont les briques de l'idée suivante : masquer l'état derrière les méthodes d'une classe. Le chapitre sur l'encapsulation rassemble ces éléments.