Ch. 4 — Encapsulation, surcharge, polymorphisme
Encapsulation par délégation et ses quatre objectifs, surcharge et chaînage de constructeurs, ArrayList face au tableau — puis la mécanique du polymorphisme : type de la référence, classe réelle et late binding.
Encapsulation par délégation
L'interface publique d'une classe est la liste des attributs et méthodes accessibles depuis l'extérieur ; encapsuler, c'est masquer les détails d'implémentation en limitant ce qui y figure. Mais que faire quand une classe existante répond « presque » au besoin — il manque des attributs, des méthodes, ou un accès modifié ? On crée une classe qui contient l'autre :
- l'attribut principal (la classe encapsulée) est
private; - des méthodes déléguées reproduisent, si besoin, le comportement de la classe encapsulée : même signature, et elles invoquent simplement l'objet encapsulé ;
- de nouveaux membres complètent le tout.
public class Worker {
private Person pers;
private double wages;
// constructeurs en délégation
public Worker(String s, LocalDate d, double v) {
this.pers = new Person(s, d);
this.wages = v;
}
// accesseur délégué
public String getName() { return this.pers.getName(); }
// traitement délégué
public boolean olderThan(Worker s) { return this.pers.olderThan(s.pers); }
// accesseur spécifique
public double getWages() { return this.wages; }
}Le détail qui montre que la délégation est maîtrisée : aucun paramètre des méthodes
de Worker n'est de type Person — la classe encapsulée n'apparaît jamais à
l'extérieur, elle est un détail d'implémentation.
Quatre objectifs de la délégation
Le cours décline l'encapsulation en quatre intentions, visibles dans ce que la classe enveloppante expose :
- Extension : enrichir les fonctionnalités par de nouveaux attributs ou méthodes —
Workerajoute un salaire àPerson; - Masquage : ne pas déléguer une méthode la rend indisponible depuis l'extérieur — on restreint les fonctionnalités ;
- Simplification : faciliter l'usage en multipliant les méthodes d'accès, avec de nouvelles signatures pour la même fonctionnalité ;
- Sécurisation : intercaler une procédure de contrôle avant d'invoquer la méthode déléguée, au lieu de l'exposer telle quelle.
Surcharge et chaînage
Surcharger (overload), c'est définir plusieurs méthodes de même nom dans une même
classe, en variant le nombre, le type ou l'ordre des paramètres — jamais le
type de retour : void processing(int) et boolean processing(int) ne peuvent pas
coexister.
La surcharge mène vite à de la duplication — l'ennemi du développeur. Le chaînage l'évite : une définition en invoque une autre, qui porte les valeurs par défaut, et le traitement n'apparaît qu'une seule fois.
void processing() { this.processing(0, "defaultValue"); }
void processing(int i) { this.processing(i, "defaultValue"); }
void processing(int i, String s) { /* le traitement, une seule fois */ }Les constructeurs se chaînent de la même façon avec this(...) — « un autre usage du
this » — pour fournir plusieurs manières de construire les instances sans répéter les
affectations. Une contrainte forte : l'appel à this(...) doit être la première
instruction du constructeur, ce qui interdit tout prétraitement des paramètres avant
l'appel : découper un nom complet en nom et prénom puis appeler
this(name, forename) ne compile pas.
ArrayList : la structure qui grandit
Le tableau a deux qualités — accès direct au k-ième élément, recherche et tri efficaces —
et un défaut : sa taille fixe, à préciser dès l'instanciation, alors que l'information
n'est pas toujours disponible à l'avance. ArrayList<E> garde l'accès direct et gère la
croissance elle-même, au prix d'une réallocation coûteuse en cas de dépassement de
capacité et d'opérations courantes peu efficaces. Ses méthodes usuelles : add (et sa
surcharge add(int index, E element) pour une insertion positionnée), get, set,
remove, size, isEmpty, clear.
Toujours typer ses ArrayList
Sans type paramétré, la liste stocke des Object et accepte "alice" et 42 côte à
côte — mais tout traitement itéré échoue : al.get(i) + 1 ne compile plus, car +
n'est pas défini sur Object. La pseudo-solution instanceof + cast par type fonctionne,
mais reste à fuir : c'est exactement le problème que les génériques (ch. 10) règlent.
Le polymorphisme par le typage
Tout objet Java est aussi un Object : un objet peut donc avoir plusieurs types. Et
la référence n'est pas l'objet :
- le type de la référence définit les envois de messages autorisés ;
- la classe de l'objet définit le traitement exécuté.
Object o = new String("myString");
System.out.println(o); // affiche la chaîne...
// o.length(); // ... mais ne compile pas : length() n'existe pas dans ObjectC'est le late binding : le compilateur ne vérifie que la cohérence entre le type de la référence et le message invoqué ; le code appelé n'est déterminé qu'à l'exécution. Les upcasts (vers plus générique) sont implicites et sûrs ; les downcasts (vers plus spécifique) sont explicites et peuvent échouer à l'exécution.
Ce mécanisme éclaire deux points du chapitre. D'abord pourquoi une ArrayList non typée
affiche correctement ses éléments : toString() est défini dans Object, donc invocable
sur tout objet, et le code exécuté est celui du type réel. Ensuite pourquoi equals
doit respecter la signature boolean equals(Object) : la version simplifiée
equals(Student) n'est pas une redéfinition et casse Arrays.equals comme
assertEquals des tests.
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (this.getClass() != obj.getClass()) return false;
Student other = (Student) obj;
return this.nip == other.nip
&& this.name.equals(other.name)
&& this.forename.equals(other.forename);
}L'annotation @Override signale qu'on redéfinit un comportement par défaut hérité de
Object — classe sans aucun attribut, qui fournit aussi getClass() et clone().
A retenir
- encapsuler par délégation : étendre, masquer, simplifier ou sécuriser ;
- surcharge à la signature près, jamais par le retour ; chaînage en première instruction ;
- la référence autorise, l'objet exécute : c'est le late binding ;
equals(Object)est la seule signature qui s'intègre aux boîtes à outils et aux tests.
Exposer la classe encapsulée dans une signature publique révèle l'implémentation : un client pourrait conserver la référence reçue et la modifier hors de tout contrôle. La délégation traduit chaque appel dans le vocabulaire de la classe enveloppante, qui reste seule responsable de son objet interne.
Non : l'invocation d'un autre constructeur doit être la première instruction. Pour déduire des valeurs (découper un nom complet, calculer une date), il faut soit une méthode statique appliquée directement dans l'argument du chaînage, soit revoir la répartition des constructeurs.
ArrayList dès que la taille n'est pas connue à l'avance ou que les ajouts sont fréquents. Le tableau reste pertinent pour une taille fixe et un accès direct intensif — la réallocation d'une ArrayList en croissance a un coût, et sa concaténation aussi.
Ce n'est pas une redéfinition de Object.equals mais une surcharge : les boîtes
à outils et les frameworks de test invoquent equals(Object) — ils appelleraient donc
la version héritée, qui compare les adresses mémoire, et non la vôtre.
coursOO_04.pdf
R2.01 · Cours 4 — Encapsulation, surcharge, chaînage et polymorphisme · 35 diapositives
Chargement du document…
Dernière mise à jour le
Ch. 3 — Statique, final et énumérations
Attributs et méthodes de classe, constantes final, blocs d'initialisation statiques, boîtes à outils Arrays et Collections, et les énumérations Java : de vraies classes aux instances figées.
Ch. 5-6 — Héritage et mécanismes fins
extends, super et protected pour factoriser l'état et le comportement, type réel contre type effectif, lookup dynamique et masquage d'attributs — jusqu'aux classes abstraites.