Comment fonctionne le mot-clé final en Java ?
Le mot-clé final est l'un des modificateurs les plus mal compris en Java. Contrairement à une idée reçue, il ne rend pas un objet immuable — il empêche simplement la réaffectation d'une référence, la surcharge d'une méthode ou l'héritage d'une classe, selon le contexte où il est placé.
1. final sur une variable
Une variable déclarée final ne peut être affectée qu'une seule fois.
final int age = 30;
age = 31; // ❌ Erreur de compilation
final List<String> liste = new ArrayList<>();
liste = new ArrayList<>(); // ❌ interdit : réaffectation
liste.add("bonjour"); // ✅ autorisé : on mute l'objet
Pour une variable primitive, final équivaut à une constante. Pour une référence d'objet, seule la référence est figée — l'objet pointé reste modifiable. C'est le piège le plus courant : final ne crée pas d'immuabilité.
2. final sur un champ d'instance
Un champ final doit être initialisé soit à la déclaration, soit dans le constructeur. Il est idéal pour modéliser des objets value-based :
public class Point {
private final int x;
private final int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() { return x; }
public int getY() { return y; }
}
Tous les champs d'un record (Java 16+) sont implicitement final.
3. final sur une méthode
Empêche une sous-classe de surcharger la méthode. Utile pour figer un algorithme critique ou un invariant de sécurité :
public class Compte {
public final void verrouiller() {
// Méthode impossible à redéfinir dans une sous-classe
}
}
public class CompteEpargne extends Compte {
@Override
public void verrouiller() { // ❌ Erreur de compilation
// ...
}
}
4. final sur une classe
Empêche toute extension. Les exemples les plus célèbres sont String, Integer et toutes les classes wrapper :
public final class Token {
private final String valeur;
public Token(String v) { this.valeur = v; }
}
public class TokenEtendu extends Token { } // ❌ Erreur de compilation
Utile pour garantir qu'une classe respecte toujours son contrat, notamment pour les classes immuables ou les classes utilitaires (Math, Collections).
5. final sur un paramètre de méthode
Indique qu'on ne réaffectera pas le paramètre à l'intérieur de la méthode :
public void traiter(final String entree) {
entree = entree.trim(); // ❌ interdit
String propre = entree.trim(); // ✅ ok
}
Purement cosmétique sur le plan comportemental, mais recommandé dans un style strict.
« effectively final » : le cousin discret
Depuis Java 8, une variable locale utilisée dans une lambda ou une classe anonyme n'a pas besoin d'être déclarée final — il suffit qu'elle ne soit jamais réaffectée. On parle alors de variable effectively final :
String prefixe = "[INFO] "; // non marquée final, mais jamais réaffectée
List.of("a", "b", "c").forEach(s -> System.out.println(prefixe + s));
// ✅ compile : prefixe est effectively final
Comment rendre un objet réellement immuable ?
Si votre objectif est l'immuabilité, final seul ne suffit pas. Vous devez :
- Déclarer la classe
final(pour empêcher l'héritage). - Déclarer tous les champs
private final. - Ne fournir aucun setter.
- Dans le constructeur, copier défensivement les collections et dates modifiables.
- Dans les getters, retourner des copies ou des vues non modifiables.
Ou, beaucoup plus simple, utiliser un record (Java 16+) :
public record Point(int x, int y) { }
Le compilateur génère automatiquement le constructeur, les accesseurs, equals, hashCode et toString, et tous les champs sont final.