L'encapsulation en Java
Regroupez données et méthodes dans des classes Java et masquez les détails d'implémentation grâce aux champs privés et aux accesseurs publics.
L'encapsulation est le premier des quatre piliers de la POO, et le plus facile à mettre en œuvre concrètement : garder les données d'une classe privées, et exposer le comportement via des méthodes. La classe devient responsable de son propre état — aucun code extérieur ne peut le mettre dans un état incohérent — et le reste du programme lui parle à travers une interface que vous contrôlez.
Le mécanisme repose sur des champs private et des méthodes public. La discipline consiste à « ne pas exposer un état que vous ne pouvez pas contrôler ».
Le schéma
Une classe non encapsulée est simplement un ensemble de champs publics :
public class Account {
public int balance;
}
Account a = new Account();
a.balance = 100;
a.balance = -50; // nothing stops thisLa version encapsulée masque le champ et expose des opérations délibérées :
public class Account {
private int balance;
public int balance() { return balance; }
public void deposit(int amount) {
if (amount <= 0) throw new IllegalArgumentException();
balance += amount;
}
public boolean withdraw(int amount) {
if (amount <= 0) throw new IllegalArgumentException();
if (amount > balance) return false;
balance -= amount;
return true;
}
}Désormais, aucun code extérieur ne peut affecter une valeur négative à balance, l'écraser sans passer par la validation, ni le consulter sans passer par balance().
Ce que vous y gagnez
Des invariants fiables. La classe Account impose la règle « balance ≥ 0 après chaque opération publique ». Comme rien à l'extérieur ne peut toucher balance, la règle est applicable depuis un seul fichier, et non dans toute la base de code.
La liberté de changer. Avec balance en privé, vous pouvez plus tard changer son type de int en long, l'unité de dollars en centimes, le stocker dans un BigDecimal, ou le déplacer vers une base de données — sans modifier un seul appelant. Avec un champ public, le type et le stockage sont gravés dans chaque site d'appel.
Une API plus petite et plus claire. Les appelants ne voient que deposit, withdraw, balance — pas la douzaine d'auxiliaires privés qui les font fonctionner. La classe annonce ce qu'elle fait, pas comment.
Un raisonnement localisé. Quand quelque chose tourne mal avec balance, le bug se trouve dans l'une des trois méthodes, et non dans l'un des mille endroits qui pourraient autrement écrire le champ.
L'encapsulation ≠ getters et setters pour tout
Un anti-pattern courant consiste à générer mécaniquement un getter et un setter pour chaque champ :
public class Account {
private int balance;
public int getBalance() { return balance; }
public void setBalance(int v) { this.balance = v; } // same as public field, with extra steps
}C'est techniquement « encapsulé » au sens des manuels, mais cela n'apporte aucun des avantages réels de l'encapsulation — n'importe qui peut encore mettre l'objet dans n'importe quel état. La vraie encapsulation exprime des opérations, pas un accès brut aux champs :
deposit(amount)plutôt quesetBalance(balance + amount)withdraw(amount)retournant succès/échec plutôt quesetBalance(balance - amount)balance()(un accesseur en lecture seule) sanssetBalancecorrespondant
Le prochain chapitre sur les getters et setters détaille les conventions pour savoir quand chacun est approprié.
Les copies défensives
Si le type d'un champ est mutable (un tableau, une liste, un Date), le retourner directement fait fuir le contrôle :
public class Order {
private final List<String> items = new ArrayList<>();
public List<String> items() { return items; } // leak!
}
Order o = new Order();
o.items().add("apple"); // outside code mutated the orderLa solution consiste à retourner une vue non modifiable ou une copie défensive :
public List<String> items() {
return List.copyOf(items); // immutable snapshot
}Le même soin s'applique aux setters qui prennent des valeurs mutables — copiez-les à l'entrée :
public Order(List<String> items) {
this.items = new ArrayList<>(items);
}Le chapitre sur les classes immuables approfondit ce sujet.
Choisir le bon niveau d'accès
private et public sont les deux que vous utilisez le plus souvent, mais Java en comporte quatre, et les niveaux intermédiaires ont leur importance pour l'encapsulation :
private— visible uniquement à l'intérieur de la même classe. La valeur par défaut pour les champs et les méthodes auxiliaires.- package-private (sans mot-clé) — visible pour les autres classes du même package. Utile quand quelques classes coopérantes forment une unité et doivent voir leurs membres internes, mais pas le monde extérieur.
protected— package-private plus visible pour les sous-classes. À réserver aux membres qu'une sous-classe a genuinement besoin de surcharger ou d'étendre.public— visible partout. C'est votre API publiée ; une fois qu'un code externe en dépend, le modifier brise les appelants.
La règle générale est le principe du moindre accès : donnez à chaque membre l'accès le plus restreint qui permet encore au code de fonctionner, et élargissez seulement lorsqu'un besoin concret apparaît. Un champ ou une méthode public est une promesse que vous devez tenir. Consultez le chapitre sur les modificateurs d'accès pour le tableau complet.
L'encapsulation dans la conception
Au-delà d'une certaine taille, l'encapsulation devient un outil de conception, pas seulement une règle de codage. Chaque classe trace une frontière autour d'un ensemble d'état et des opérations sur celui-ci ; le reste du système lui parle à travers cette frontière. La plupart des patterns architecturaux — en couches, hexagonal, MVC, Clean — sont des arguments sur où tracer les frontières.
Directives pratiques :
- Les champs sont privés par défaut. Commencez par là ; élargissez seulement avec une raison.
- Exposez des verbes, pas des noms.
cancel(),pay(),ship()valent mieux quesetStatus(...). - Ne retournez pas les membres internes mutables tels quels. Encapsulez, copiez, ou utilisez une vue immuable.
- Validez à l'entrée. Rejetez les états impossibles à la frontière afin que le code interne puisse supposer qu'il est valide.
Un exemple complet
La suite
La partie mécanique de l'encapsulation — les champs private avec les méthodes public qui les lisent ou les écrivent — a ses propres conventions qui méritent d'être connues. Continuez avec les getters et setters.