Java orienté objet
Guide de révision personnalisé à partir des CM 02 à 09, enrichi avec les
examens corrigés 2024 et 2025
Pour un étudiant qui sait déjà coder, mais qui veut enfin parler le langage exact attendu à
l’examen, comprendre les règles spécifiquement javiennes, et éviter les pièges classiques de
partiel.
Ce document ne réexplique pas l’informatique depuis zéro. Il repart de ce que tu sais déjà
faire, puis met des mots scolaires précis sur les idées.
Le fil directeur est volontairement narratif : on suit une progression logique, mais on insiste
davantage sur les notions qui donnent des points à l’examen.
Quand une formulation usuelle simplifie un peu Java, j’ajoute la nuance utile pour éviter une
réponse fausse le jour J.
Plan conseillé pour utiliser ce document
Lis d’abord les sections 1 à 4 pour remettre les concepts d’OO en place. Reviens ensuite sur les
sections 5 à 8 pour les règles purement Java, les choix d’architecture et les points où compilation
et exécution ne disent pas la même chose. Enfin, termine par la fiche ultra-rapide : elle a été
pensée pour la dernière heure avant l’épreuve.
Idée directrice — l’orienté objet n’est pas seulement une manière d’écrire du code ; c’est
surtout une manière de modéliser un problème en répartissant les responsabilités entre des
objets.
1. L’OO comme manière de voir un problème
Un langage de programmation n’est pas qu’une syntaxe : il traduit une certaine manière de
penser la résolution d’un problème. Dans le paradigme impératif classique, on décrit surtout
une suite de commandes à exécuter. Dans l’orienté objet, on décrit un monde composé d’objets
qui possèdent un état, qui savent répondre à certains messages, et qui coopèrent entre eux.
Le mot important ici est paradigme. À l’examen, il faut savoir dire qu’un paradigme est une
manière cohérente de représenter un problème et de le résoudre. L’OO est présenté comme une
extension du paradigme impératif : on programme toujours des actions, mais on les organise
autour d’objets, de classes et d’interactions.
Historiquement, cette idée remonte à Simula, langage né en 1967 pour simuler des robots dans
une usine. Le message pédagogique est simple : plus les interactions sont nombreuses, plus un
pilotage centralisé devient pénible. L’OO apporte alors une décomposition plus naturelle du
système.
Vocabulaire à employer — paradigme, modélisation, objet, classe, attribut, méthode,
message, instance, instanciation, environnement d’un objet.
Java orienté objet — guide de révision personnalisé
Objets, messages et méthodes
Un objet est une entité informatique qui communique par envoi de messages. Ce n’est pas
seulement une structure de données : c’est aussi un comportement. La classe décrit ce qu’un
objet de ce type contient, c’est-à-dire ses attributs, et comment il répond, c’est-à-dire ses
méthodes.
Le mot message mérite d’être retenu. Dire « appeler une méthode » est correct en Java ; dire «
envoyer un message à un objet » est la formulation conceptuelle orientée objet la plus naturelle
pour décrire l’interaction entre objets.
Non-intrusion et délégation
Deux principes sont martelés très tôt. Le premier est la non-intrusion : on évite d’agir
directement sur l’état interne d’un objet depuis l’extérieur. Le second est la délégation : au lieu
de faire soi-même le travail sur l’objet, on demande à l’objet de le faire. Dans l’exemple de
l’annuaire, le bouton Clear ne vide pas lui-même les champs ; il demande à la fenêtre principale
de s’en charger.
Ces deux idées préparent toute la suite : encapsulation, visibilité, MVC, héritage bien utilisé,
interfaces et même gestion propre des responsabilités.
2. Le vocabulaire Java indispensable avant tout le reste
Le deuxième bloc passe des idées générales à la matière brute du langage. Même si tu sais déjà
coder, c’est ici que se joue une partie des questions scolaires : type scalaire, type référence,
constructeur, variable de classe, méthode de classe, point d’entrée du programme, portabilité,
etc.
Types primitifs et types références
Java est présenté comme un langage fortement typé. Toute variable possède un type déclaré. Il
faut distinguer d’abord les types de base, dits scalaires ou primitifs : char, byte, short, int, long,
float, double et boolean. Ensuite viennent les types références, c’est-à-dire les variables qui ne
contiennent pas directement un objet, mais une référence vers cet objet.
La distinction est essentielle. Un int contient une valeur entière. Une variable de type Point
contient une référence vers un objet Point. Quand tu écris Point p = new Point(...);, la variable p
ne contient pas le point lui-même : elle contient de quoi retrouver cet objet en mémoire.
int n = 5;
Point p = new Point(10, 20);
Piège classique — pour les références, null signifie qu’aucun objet n’est référencé. Envoyer
un message à une référence nulle provoque une erreur d’exécution, typiquement une
NullPointerException.
Java orienté objet — guide de révision personnalisé
Initialisation par défaut : la nuance utile
Java fournit des valeurs par défaut : 0 pour les types numériques, false pour boolean, null pour
les références. Il faut le retenir, mais avec une nuance très importante : en pratique, cette
initialisation par défaut vaut pour les attributs d’instance, les attributs de classe et les éléments
de tableaux, pas pour les variables locales d’une méthode, que le compilateur oblige à initialiser
avant usage.
Cette nuance vaut de l’or le jour d’un QCM : elle permet de ne pas répéter trop vite une phrase
qui serait vraie « en gros » dans les conventions, mais fausse si on l’applique partout.
Affectation, égalité, création d’objet
En Java, = est l’opérateur d’affectation et == l’opérateur de test d’égalité. C’est une banalité
apparente, mais elle revient ensuite avec une vraie conséquence pour les références : avec deux
objets, == teste l’identité de référence, pas l’égalité de contenu.
La création d’objet passe par new. L’instruction new fabrique l’objet, puis renvoie une référence
sur cet objet. Cette référence est ensuite stockée dans la variable placée à gauche du =.
Constructeurs et mot-clé this
Un constructeur est une sorte de méthode particulière qui porte le même nom que la classe et
qui ne possède pas de type de retour. Son rôle est d’initialiser correctement l’objet lors de son
instanciation. Une classe peut définir plusieurs constructeurs : on parle alors de surcharge de
constructeurs. Le constructeur effectivement choisi dépend du nombre et du type des
arguments donnés à new.
Le mot-clé this a deux usages importants dans ce chapitre. D’abord, il permet de lever
l’ambiguïté entre un attribut et un paramètre portant le même nom. Ensuite, un constructeur
peut appeler un autre constructeur de la même classe via this(...). Dans ce cas, cet appel doit être
la première instruction du constructeur appelant.
public class Point {
private int xCoord;
private int yCoord;
public Point(int xCoord, int yCoord) {
[Link] = xCoord;
[Link] = yCoord;
}
public Point(double rho, double theta) {
this((int) (rho * [Link](theta)),
(int) (rho * [Link](theta)));
}
}
Variables d’instance, variables de classe, méthodes de classe
Il faut bien distinguer ce qui appartient à chaque objet et ce qui appartient à la classe. Un
attribut ordinaire est une variable d’instance : chaque objet possède sa propre valeur. Un
Java orienté objet — guide de révision personnalisé
attribut déclaré static est une variable de classe : il n’y en a qu’une seule, partagée entre toutes
les instances.
Même logique pour les méthodes. Une méthode static est exécutée par la classe elle-même. Elle
peut accéder directement aux variables de classe, mais pas aux variables d’instance, sauf si on
lui donne explicitement une instance sur laquelle travailler.
À retenir — static signifie « lié à la classe » ; sans static, on parle d’éléments liés aux
instances.
public class Point {
private static int numberOfPoints = 0;
private int xCoord;
private int yCoord;
public Point(int xCoord, int yCoord) {
[Link] = xCoord;
[Link] = yCoord;
numberOfPoints++;
}
public static void writeNumberOfPoints() {
[Link](numberOfPoints);
}
}
Le point d’entrée, le JDK et la JVM
Le point d’entrée attendu par Java est la méthode public static void main(String[] args). Cela
permet aussi de rappeler que Java s’appuie sur un écosystème complet : le JDK fournit le
compilateur et les bibliothèques standard ; la JVM exécute le bytecode produit par la
compilation. C’est cette machine virtuelle qui porte la fameuse idée « compile once, run
everywhere ».
Autrement dit, Java ne produit pas directement un exécutable spécifique au processeur comme
dans une chaîne classique. Il produit un code destiné à la JVM, ce qui explique la portabilité du
langage.
Conventions de nommage
Il faut respecter des conventions de nommage simples : noms de classes en majuscule initiale,
noms de méthodes, variables et attributs en minuscule initiale, noms descriptifs et si possible en
anglais. Même si cela semble stylistique, c’est un vrai élément de lisibilité et donc de qualité
logicielle.
Java orienté objet — guide de révision personnalisé
3. Encapsulation, visibilité et portée : le cœur du langage
scolaire de l’OO
Si tu ne devais retenir qu’un seul grand thème technique ici, ce serait probablement celui-ci.
L’encapsulation est fondamentale : l’état d’un objet doit être protégé, et les autres objets doivent
passer par l’interface publique de cet objet pour le consulter ou le modifier.
Pourquoi encapsuler ?
Encapsuler, c’est déclarer les attributs private afin que seul l’objet lui-même puisse y accéder
directement. Cela évite les modifications sauvages, permet de valider les valeurs, de préserver
la cohérence interne des données et de rendre l’implémentation modifiable sans casser le reste
du code.
Un setter peut par exemple vérifier qu’un âge reste entre des bornes raisonnables, ou une
méthode de mise à jour d’un vecteur peut recalculer automatiquement sa longueur pour
conserver la cohérence entre les attributs.
Formulation attendue — l’encapsulation protège l’état de l’objet, garantit l’intégrité des
données et rend l’implémentation transparente pour les utilisateurs de la classe.
Getters, setters, accesseurs, mutateurs
Pour lire une donnée encapsulée, on fournit un getter, aussi appelé accesseur. Pour la modifier,
on fournit un setter, aussi appelé mutateur. Le point important n’est pas le préfixe get ou set en
lui-même, mais le fait que toute lecture ou écriture passe par une méthode où l’on peut
contrôler ce qui se passe.
Les quatre visibilités à savoir nommer
Visibilité Accessible depuis Commentaire à retenir
fait partie de l’interface
public partout
visible de la classe
outil principal de
private la classe elle-même
l’encapsulation
valeur par défaut si rien n’est
package les classes du même package
précisé
les sous-classes et le même
protected à manier avec prudence
package
Il faut insister sur un point souvent mal retenu : protected n’est pas un « private un peu plus
large » anodin. Pour des attributs, l’utiliser casse en partie l’encapsulation, car une sous-classe
ou une classe du même package peut modifier directement la donnée sans contrôle. La
recommandation à retenir est donc claire : préférer private pour les attributs, et réserver
protected surtout à certaines méthodes destinées aux sous-classes.
Java orienté objet — guide de révision personnalisé
Packages, imports et organisation du code
Un package est une collection logique de classes. Il sert à structurer le programme, à éviter les
collisions de noms et à regrouper ce qui doit évoluer ensemble. L’idée directrice est celle de la
cohésion forte et du couplage faible : on rassemble ce qui travaille ensemble, on sépare ce qui
n’a que peu de dépendances.
Par défaut, une classe d’un autre package doit être désignée par son nom complètement
qualifié, sauf si on utilise import. Les noms de packages sont hiérarchiques, souvent inspirés du
nom de domaine, et écrits en minuscules.
Portée des déclarations
La portée n’est pas la même chose que la visibilité. La visibilité répond à la question « qui a le
droit d’accéder ? ». La portée répond à la question « où le nom existe-t-il dans le code ? ». Une
variable locale n’existe qu’à partir de sa ligne de déclaration et jusqu’à la fin du bloc courant. Un
paramètre existe dans toute la méthode. Un attribut existe dans toute la classe.
Passage de paramètres : toujours par valeur
Java pratique le passage de paramètres par valeur. C’est un point classique d’examen. Il faut
cependant savoir l’expliquer correctement. Quand on passe un int, on copie la valeur entière.
Quand on passe une référence, on copie la référence. On ne passe donc jamais « l’objet lui-
même » comme tel ; on passe la copie d’une référence vers cet objet.
Formule propre — Java est pass-by-value, y compris pour les objets : ce qui est copié, c’est la
référence, pas l’objet.
String, égalité et ArrayList
Les chaînes de caractères sont des objets de la classe String. La concaténation peut se faire avec
+ ou avec concat. Surtout, == ne sert pas à tester si deux chaînes ont le même contenu, mais si
les deux références pointent vers le même objet. Pour comparer le contenu, il faut utiliser
equals().
String a = "Java";
String b = new String("Java");
boolean sameObject = (a == b); // faux en général
boolean sameContent = [Link](b); // vrai
ArrayList<E> est une classe générique : E représente le type des éléments stockés. Une ArrayList
contient des références vers des objets de type E, s’allonge automatiquement, indexe ses
éléments à partir de 0 et propose notamment add, remove, get et size.
Même si tu sais déjà l’utiliser, retiens le vocabulaire : classe générique, paramètre de type,
structure indexée, parcours classique par index, parcours simplifié via for généralisé.
ArrayList<Robot> robots = new ArrayList<>();
for (Robot robot : robots) {
[Link](robot);
}
Java orienté objet — guide de révision personnalisé
4. Héritage, redéfinition, polymorphisme et liaison
dynamique
L’héritage de classe est présenté comme une relation entre une classe mère, ou super-classe, et
une classe fille, ou sous-classe. Le mot-clé est extends. La sous-classe hérite des déclarations de
la classe mère, puis peut soit enrichir la classe avec de nouveaux attributs et de nouvelles
méthodes, soit redéfinir des méthodes héritées, soit faire les deux.
Enrichissement, redéfinition, super
Enrichir signifie ajouter des éléments nouveaux dans la sous-classe. Redéfinir signifie fournir
une nouvelle implémentation d’une méthode déjà héritée. Pour appeler explicitement un
constructeur de la classe mère, on utilise super(...), et cet appel doit être la première instruction
du constructeur de la sous-classe.
public class ColouredPoint extends Point {
private Color color;
public ColouredPoint(int xCoord, int yCoord, Color color) {
super(xCoord, yCoord);
[Link] = color;
}
}
Le mot-clé super sert aussi à appeler une méthode héritée quand on veut réutiliser le
comportement de la classe mère avant ou après l’avoir complété.
Polymorphisme
Le polymorphisme vient du fait qu’un même objet peut être vu sous plusieurs types compatibles
avec sa place dans l’arbre d’héritage. Un Square est un Square, mais aussi un Rectangle et aussi
un Shape. Cela autorise des déclarations plus générales, par exemple une liste de Shape
contenant des Rectangle, Circle, Point et Square.
À l’examen, la bonne formulation est la suivante : un objet d’une sous-classe possède aussi les
types de toutes ses classes mères.
Liaison dynamique
Une fois le polymorphisme admis, il faut répondre à la question suivante : si une variable est
déclarée du type Shape, quelle méthode print() est exécutée lorsqu’elle référence en réalité un
Circle ou un Rectangle ? La réponse est que Java pratique la liaison dynamique. La méthode
appelée est choisie à l’exécution en fonction de la classe réelle de l’objet.
C’est précisément ce qui rend possible l’écriture élégante de boucles polymorphes.
for (Shape shape : shapes) {
[Link]();
}
Java orienté objet — guide de révision personnalisé
Mots justes — recherche dynamique de méthode, liaison dynamique, type déclaré de la
variable, classe réelle de l’objet à l’exécution.
toString()
Rappel utile : lorsqu’on affiche un objet avec [Link](monObjet), le mécanisme passe
par toString(). Si tu as redéfini toString() dans ta classe, la liaison dynamique fera en sorte que
ce soit ta version qui soit utilisée.
Le mot-clé final
final a plusieurs usages, et il faut tous les connaître. Sur une méthode, final interdit la
redéfinition par les sous-classes. On peut aussi le comprendre du point de vue de l’optimisation :
si une méthode ne pourra jamais être redéfinie, le compilateur peut éviter certains coûts liés à
la liaison dynamique.
Sur une classe, final interdit l’héritage. L’exemple classique est String : on ne peut pas sous-
classer String. Sur un attribut, final signifie qu’il ne peut être affecté qu’une seule fois. Sur un
paramètre ou une variable locale, final veut dire qu’on s’interdit toute réaffectation par la suite.
Emplacement de final Effet Image mentale utile
méthode pas de redéfinition le comportement est figé
classe pas d’héritage la hiérarchie s’arrête ici
attribut une seule affectation donnée non réassignable
variable locale / paramètre pas de réaffectation sécurité d’écriture
Attention : final sur une référence n’implique pas que l’objet pointé soit immuable ; cela signifie
seulement que la référence elle-même ne changera plus. En revanche, si les attributs internes
de l’objet sont eux aussi final et qu’aucune méthode ne les modifie, on obtient un objet
effectivement immuable.
5. Classes abstraites, méthodes abstraites et bonne
modélisation
L’héritage se prolonge naturellement avec les classes abstraites. Une classe abstraite modélise
une catégorie logique utile dans l’arbre de classes, mais qu’on n’a pas de raison d’instancier
directement. On la déclare avec abstract. Une tentative de new sur une classe abstraite
provoque une erreur de compilation.
Une méthode abstraite, elle, est déclarée sans corps. On impose donc aux sous-classes concrètes
de fournir une implémentation, sauf si une classe abstraite intermédiaire le fait déjà.
public abstract class Item {
private final double netPrice;
public Item(double netPrice) {
[Link] = netPrice;
}
Java orienté objet — guide de révision personnalisé
public final double getNetPrice() {
return netPrice;
}
public abstract double getVAT();
public final double getATIPrice() {
return getNetPrice() + getVAT();
}
}
Cet exemple condense une idée importante : une classe abstraite peut contenir à la fois du
comportement concret et du comportement abstrait. Ici, getATIPrice() est concret, mais dépend
de getVAT(), qui doit être fourni plus bas dans la hiérarchie.
Pourquoi abstraire ?
Abstraire sert à clarifier le modèle. Si la classe Shape ne représente qu’une catégorie générale,
on évite de créer directement un « Shape » nu. On préfère créer des Circle, Rectangle, Point, etc.
De même, une classe Item peut regrouper le prix net et le calcul du prix TTC sans qu’un objet «
item générique » ait un sens métier complet.
Éviter la duplication sans sur-complexifier
On peut modéliser la TVA des articles de deux manières : soit chaque sous-classe redéfinit
getVAT(), soit le taux de TVA est stocké dans un attribut de la classe mère. La leçon à retenir
n’est pas qu’un modèle est absolument meilleur, mais qu’un bon informaticien surveille la
duplication de code tout en restant attentif à la complexité qu’il introduit.
Autrement dit : factoriser est bien, mais pas à n’importe quel prix. C’est déjà un raisonnement
de conception.
Héritage ou composition ?
L’un des messages les plus importants du chapitre est que l’héritage ne doit pas être utilisé dès
qu’un objet « possède » quelque chose. Le bon test est souvent sémantique : est-ce que l’objet fils
est vraiment une sorte de l’objet parent ? Un unicycle possède une roue, mais n’est pas une
roue. Ici, il faut de la composition, pas de l’héritage.
Formule utile — héritage = relation « est une sorte de » ; composition = relation « possède /
contient / utilise ».
Principe de substitution de Liskov
Barbara Liskov formule ici un principe central : une sous-classe doit pouvoir être utilisée à la
place de sa classe mère sans comportement inattendu. C’est le principe qui rend la relation
Square/Rectangle conceptuellement délicate dès qu’on raisonne avec des setters de largeur et de
hauteur indépendants.
Java orienté objet — guide de révision personnalisé
Si remplacer un Rectangle par un Square casse les hypothèses du code appelant, alors l’héritage
devient discutable, même si l’intuition géométrique « un carré est un rectangle » paraît
séduisante.
Responsabilité unique
Autre principe fondamental : une classe ne doit changer que pour une seule raison. C’est le
principe de responsabilité unique. Une classe Computer qui sait représenter un ordinateur et
qui sait en plus se sauvegarder elle-même dans un fichier mélange deux responsabilités : la
modélisation métier et la persistance. Il vaut mieux séparer ces rôles.
C’est une excellente boussole de partiel : si une question te demande si une méthode « a sa place
» dans une classe, demande-toi si elle relève vraiment de la responsabilité principale de cette
classe.
6. Interfaces : contrat, abstraction, collections et
programmation à l’interface
Le mot interface est employé ici au sens logiciel–logiciel, c’est-à-dire API. Une interface Java
décrit des signatures de méthodes sans imposer la manière exacte dont elles sont implémentées.
Une classe qui implémente une interface s’engage à fournir ces méthodes visibles.
Interface d’un objet et interface Java
Le point de départ est simple : pour communiquer avec un objet, il faut connaître les messages
qu’il sait recevoir. En Java, on peut matérialiser cette « surface de communication » par une
interface déclarée avec interface. Une interface n’est donc pas une classe : elle spécifie un
contrat.
public interface Canvas {
String getName();
int getWidth();
int getHeight();
}
Une classe concrète utilisera implements pour promettre qu’elle respecte ce contrat.
Les quatre grands rôles des interfaces
outil de conception : on pense les interfaces avant les classes, comme une spécification de
haut niveau ;
outil contractuel : une classe qui implémente l’interface s’engage à fournir les méthodes
annoncées ;
outil d’encapsulation : déclarer une variable avec le type interface masque les méthodes
spécifiques de l’implémentation ;
descriptif de propriété commune : des classes sans lien d’héritage peuvent partager une
interface comme Drawable, Flying ou Whistling.
Java orienté objet — guide de révision personnalisé
Héritage d’interfaces et multiple inheritance des contrats
Alors que Java n’autorise qu’un héritage simple de classes, il autorise une interface à hériter de
plusieurs autres interfaces. C’est une composition de contrats, pas une fusion
d’implémentations. Cela permet de construire des types abstraits riches sans retomber dans les
problèmes classiques de l’héritage multiple de classes.
S’il y a des méthodes de même nom héritées de plusieurs interfaces, la situation peut rester
simple si les signatures sont compatibles : la classe concrète n’implémente alors qu’une seule
fois la méthode. Si les signatures diffèrent, la classe doit fournir ce qu’il faut pour satisfaire les
deux contrats.
Interfaces des collections du JDK
Parmi les interfaces du JDK, il faut connaître Collection comme racine générale, List pour les
listes ordonnées indexées, Set pour les ensembles sans doublons, SortedSet pour les ensembles
ordonnés, Map pour les associations clé-valeur, et SortedMap pour les associations clé-valeur
ordonnées.
Il faut savoir distinguer l’interface et l’implémentation. ArrayList et LinkedList sont des
implémentations de List. HashMap et Hashtable sont des implémentations de Map. Une
formulation propre consiste donc à dire : « List est une interface ; ArrayList est une classe qui
l’implémente ».
Choisir l’implémentation selon le besoin
Interface Implémentations évoquées Idée principale
accès rapide par index contre
List ArrayList / LinkedList insertions/suppressions plus
faciles
performance simple contre
Map HashMap / Hashtable
synchronisation intégrée
Il faut aussi connaître les coûts d’accès. Une ArrayList donne un accès par index en temps
constant, mais les insertions ou suppressions au milieu sont coûteuses. Une LinkedList facilite
les insertions et retraits locaux, mais l’accès par index devient linéaire. C’est un raisonnement
classique d’algorithmique appliqué au design d’API.
Programmer à l’interface
C’est sans doute la règle pratique la plus importante du chapitre. Quand tu déclares une
variable, un attribut ou un type de retour, tu dois préférer l’interface à la classe concrète
chaque fois que cela a du sens. On écrit donc List<Component> plutôt que
ArrayList<Component>, puis on choisit ArrayList ou LinkedList au moment de l’instanciation.
private final List<Component> components;
public Factory() {
components = new ArrayList<>();
}
Java orienté objet — guide de révision personnalisé
Pourquoi ? Parce que cela améliore la maintenabilité. Si l’implémentation devient inadéquate,
on ne change que le point d’instanciation. Tout le reste du code continue de compiler tant qu’il
dépend uniquement du contrat commun.
Formulation à retenir — programmer à l’interface permet de faire abstraction de
l’implémentation concrète et de faciliter le remplacement d’une implémentation par une
autre.
Iterable, boucle for généralisée et interfaces marqueurs
Les collections du JDK sont itérables. Cela autorise la boucle for généralisée. Il faut aussi
connaître la notion d’interface de marquage, c’est-à-dire une interface vide comme
RandomAccess. Ici, le rôle n’est pas d’ajouter des méthodes, mais d’indiquer une propriété à
l’environnement d’exécution ou à une bibliothèque.
Dans l’exemple donné, l’algorithme de tri générique peut adapter sa stratégie selon que la
collection permet un accès aléatoire efficace. Le test se fait via instanceof RandomAccess. C’est
un bon exemple d’interface utilisée comme étiquette sémantique.
7. MVC et observateur–observable : organiser une interface
graphique proprement
Le MVC, pour Modèle–Vue–Contrôleur, est présenté comme un patron de conception pour les
interfaces utilisateur. Le modèle contient les données et la logique métier. La vue affiche ces
données. Le contrôleur fait le lien entre les actions de l’utilisateur et les modifications du
modèle.
Le modèle
Le modèle est le cœur de l’application. C’est là que résident les données qui déterminent ce que
la vue doit afficher. Dans l’exemple de l’usine robotisée, le modèle contient par exemple un
canevas et une liste de composants.
La vue
La vue, ou IHM, est la représentation graphique du modèle. Elle contient des widgets, des
fenêtres, des boutons, des menus, des zones de dessin, etc. Il faut aussi distinguer UI de UX : l’UI
concerne l’interaction et le visuel, alors que l’UX vise l’expérience globale d’utilisation.
Le contrôleur
Le contrôleur reçoit les actions de l’utilisateur, décide quoi faire et pilote le modèle en
conséquence. Une bonne manière de le voir est la suivante : le modèle sait ce qu’il est, la vue sait
montrer, le contrôleur sait coordonner une action.
Pourquoi l’observateur–observable ?
Le problème central du MVC est la synchronisation de la vue avec le modèle. Si le modèle
change, la vue doit se mettre à jour. La réponse classique est le patron observateur-observable.
Java orienté objet — guide de révision personnalisé
Le modèle, observable, conserve la liste de ses observateurs. Lorsqu’il change, il les notifie. La
vue, observatrice, se redessine alors.
public interface Observer {
void modelChanged();
}
public interface Observable {
boolean addObserver(Observer observer);
boolean removeObserver(Observer observer);
}
Les anciennes abstractions Observable et Observer du JDK étant dépréciées depuis Java 8, il est
souvent plus propre de redéfinir ses propres interfaces, ce qui constitue aussi une bonne leçon
d’architecture.
Gros grain et grain fin
Remarque de conception importante : un MVC trop gros grain peut redessiner toute la vue au
moindre changement, ce qui n’est pas toujours optimal. Une conception plus fine peut
multiplier les relations observateur-observable pour ne mettre à jour que ce qui a réellement
changé. C’est une ouverture vers des architectures plus raffinées.
8. Exceptions et logging : ce que Java fait quand quelque
chose se passe mal
Le dernier grand bloc traite des erreurs. Le point de départ est presque philosophique : mal
gérer une erreur ne crée pas seulement un crash ; cela peut aussi produire des messages
absurdes, décourageants, trompeurs, voire révéler des données sensibles.
Propagation d’une exception
Quand une erreur survient, Java crée un objet représentant cette erreur et le propage le long de
la pile d’appels. Si personne ne l’intercepte, le programme s’arrête et affiche une trace. Le
vocabulaire à connaître est le suivant : l’exception est levée, puis propagée.
Toutes les erreurs propagées sont des sous-classes de Throwable. Sous Throwable, il faut
distinguer Error pour les erreurs graves, Exception pour les erreurs ordinaires à gérer, et
RuntimeException comme sous-catégorie particulière d’Exception pour les erreurs de langage
ou d’usage courant.
Type Nature Exemple classique
grave, généralement non
Error OutOfMemoryError
récupérable
doit être gérée par le
Exception IOException
programmeur
sous-catégorie courante, non ArithmeticException,
RuntimeException
vérifiée IndexOutOfBoundsException
Java orienté objet — guide de révision personnalisé
try, catch, finally
Un bloc try délimite le code susceptible de produire une erreur. Un bloc catch récupère une
exception d’un certain type et stoppe sa propagation. Plusieurs catch peuvent coexister ; seul le
premier compatible est exécuté, d’où l’importance de l’ordre, des plus spécifiques vers les plus
généraux.
La clause finally, elle, s’exécute quoi qu’il arrive après le try-catch. L’exemple canonique est la
fermeture d’un fichier : même si la lecture échoue, le finally garantit le nettoyage.
try {
// lecture du fichier
}
catch (IOException ex) {
// message d'erreur I/O
}
catch (Exception ex) {
// autre traitement
}
finally {
// fermeture du fichier
}
printStackTrace(), throw et throws
printStackTrace() affiche la pile d’appels au moment de l’erreur. C’est utile pour comprendre où
le problème s’est produit, mais ce n’est pas un mécanisme de communication utilisateur
satisfaisant à lui seul.
Avec throw, le programmeur peut lui-même lever une exception. Avec throws, il annonce dans
la signature qu’une méthode peut propager une exception vérifiée. C’est ici qu’intervient la
distinction checked / unchecked : toute exception autre qu’une RuntimeException est considérée
comme vérifiée et doit être déclarée ou traitée.
public String computeFullName(String firstName, String lastName)
throws Exception {
if (firstName == null || [Link]()) {
throw new Exception("First name is invalid");
}
if (lastName == null || [Link]()) {
throw new Exception("Last name is invalid");
}
return firstName + " " + lastName;
}
À retenir — une exception checked doit être déclarée avec throws ou attrapée ; une
RuntimeException est unchecked et n’impose pas cette déclaration.
Java orienté objet — guide de révision personnalisé
Exceptions applicatives
Il est souvent préférable de définir des exceptions métiers plus précises, comme
InvalidFirstNameException ou InvalidLastNameException. C’est un moyen de rendre les erreurs
plus lisibles et de permettre un traitement plus fin.
Il pousse même le raisonnement jusqu’à la factorisation : si plusieurs exceptions partagent le
même cœur conceptuel, on peut définir une exception parente commune comme
InvalidNameException. Tu retrouves ici les réflexes d’architecture vus plus haut : spécialiser
proprement, éviter les duplications inutiles.
N’utiliser les exceptions que pour les situations exceptionnelles
Une comparaison très importante oppose deux versions d’une méthode findStudent. Dans la
mauvaise version, on essaie un accès à la liste puis on attrape IndexOutOfBoundsException
pour renvoyer null. Dans la meilleure version, on teste d’abord les bornes avec une condition
ordinaire. La conclusion est explicite : il ne faut pas utiliser les exceptions pour piloter le flot
normal du programme.
Formulation exacte — n’utiliser les exceptions que pour la gestion des exceptions ; pour le
flot normal, préférer les structures de contrôle ordinaires, meilleures en lisibilité et en
performances.
Logging
Le logging sert à émettre, formater et stocker des traces d’exécution. Il faut distinguer trois rôles
généraux : le logger, qui produit le message ; le formatter, qui décide de sa forme ; et l’appender
- ou handler dans le vocabulaire du JDK - qui l’envoie vers une cible comme la console ou un
fichier.
Le message de fond est clair : dès qu’un logiciel devient sérieux, [Link]() ne suffit
plus. On a besoin de niveaux de gravité, de fichiers de traces, de configuration centralisée et de
messages exploitables pour le débogage ou l’exploitation.
private static final Logger LOGGER =
[Link]([Link]());
[Link]("Starting the simulation...");
[Link]("With parameters ...");
try {
// ...
}
catch (Exception ex) {
[Link]([Link], "Error during simulation", ex);
}
Les niveaux mentionnés sont SEVERE, WARNING, INFO, CONFIG, FINE, FINER et FINEST. Un
message n’est produit que si son niveau est au moins aussi élevé que celui configuré pour
l’application ou le handler.
Java orienté objet — guide de révision personnalisé
Dans les applications complexes, il faut utiliser des loggers autant que possible et éviter l’usage
direct de [Link](...) et de [Link]() dans le code applicatif final.
9. Le tableau mental le plus rentable pour un partiel
Beaucoup d’erreurs d’examen viennent du fait qu’on mélange ce qui relève de la compilation,
de l’exécution, de la modélisation ou du style. Le tableau suivant sert précisément à remettre
ces catégories en ordre.
Situation Quand Java réagit Ce qu’il faut dire
accès à un attribut private
compilation problème de visibilité
depuis l’extérieur
une classe abstraite n’est pas
new sur une classe abstraite compilation
instanciable
oubli d’un throws pour une exception vérifiée non
compilation
checked exception déclarée
envoi d’un message à null exécution NullPointerException
index de liste hors bornes exécution IndexOutOfBoundsException
choix de la bonne méthode
exécution liaison dynamique
redéfinie
remplacement d’une
implémentation List par une conception / maintenance programmer à l’interface
autre
10. Fiche ultra-rapide à relire juste avant l’épreuve
Une classe décrit des attributs et des méthodes ; un objet est une instance de cette classe.
Une variable de type référence ne contient pas l’objet, mais une référence vers lui.
Encapsuler = cacher les attributs avec private et exposer des méthodes contrôlées.
Un getter consulte ; un setter modifie ; protected sur les attributs est déconseillé.
La portée dit où un nom existe ; la visibilité dit qui a le droit d’y accéder.
Java passe toujours les paramètres par valeur ; pour un objet, c’est la référence qui est copiée.
== teste l’identité pour les références ; equals() teste l’égalité logique.
static = lié à la classe ; sans static = lié aux instances.
extends exprime l’héritage de classe ; implements exprime l’implémentation d’une interface.
super(...) appelle le constructeur parent ; this(...) appelle un autre constructeur de la même
classe.
Le polymorphisme permet de manipuler un objet sous un type plus général.
Java choisit la méthode redéfinie à l’exécution : c’est la liaison dynamique.
abstract interdit l’instanciation directe et peut imposer des méthodes abstraites.
final peut figer une méthode, une classe, un attribut ou une variable locale.
Une interface décrit un contrat ; on programme à l’interface pour découpler le code des
implémentations.
ArrayList et LinkedList implémentent List mais n’ont pas les mêmes coûts d’accès.
MVC = modèle pour les données, vue pour l’affichage, contrôleur pour la coordination.
Observer–observable sert à mettre à jour la vue quand le modèle change.
Java orienté objet — guide de révision personnalisé
Throwable est la racine ; Error, Exception et RuntimeException sont à distinguer.
Une checked exception doit être déclarée ou traitée ; une RuntimeException est unchecked.
finally sert au nettoyage ; les exceptions ne doivent pas remplacer un simple if.
Le logger remplace avantageusement [Link]() dans une application sérieuse.
Dernier conseil — si une question te paraît floue, essaie toujours de répondre avec le
triptyque suivant : quelle est la responsabilité de l’objet, quelle est l’abstraction visible, et à
quel moment Java décide ou signale le problème — compilation ou exécution.
Appendice. Modéliser avant de coder
Le tout premier CM ne s’arrête pas à Java. Il insiste aussi sur l’idée qu’en ingénierie logicielle, on
peut viser un niveau d’abstraction encore plus haut : modéliser d’abord, puis dériver le code
ensuite. C’est le sens des acronymes CIM, PIM, PDM et PSM évoqués en fin de diaporama. Même
si ce n’est pas la partie la plus technique pour le partiel, elle donne une idée de la philosophie
générale de l’enseignement : l’OO n’est pas seulement un style de code, c’est une manière de
concevoir un système.
CIM décrit le système indépendamment du calcul ; PIM le décrit indépendamment de la
plateforme ; PDM décrit la plateforme elle-même ; PSM décrit la réalisation logicielle sur cette
plateforme. Tu n’as sans doute pas besoin de réciter tout cela en détail, mais il est utile d’en
garder l’esprit : on cherche d’abord de bonnes abstractions, puis on descend vers
l’implémentation.
Fin du guide.
Java orienté objet — guide de révision personnalisé
Complément décisif à partir des examens corrigés de 2024
et 2025
Les deux sujets corrigés montrent que l’évaluation ne se contente pas de vérifier si tu sais coder
« globalement ». Les questions courtes évaluent surtout la précision du vocabulaire, la capacité
à distinguer compilation et exécution, la compréhension du polymorphisme, de la liaison
dynamique, de l’encapsulation, des interfaces et des exceptions, puis la capacité à produire un
modèle propre dans un exercice de conception. La bonne nouvelle est que les deux années sont
très cohérentes : une fois les mécanismes bien nommés, les pièges deviennent très prévisibles.
Dans ce complément, il ne s’agit pas de remplacer le reste du guide, mais de l’orienter
explicitement vers ce que ces examens demandent réellement d’écrire. Le but est que tu puisses
non seulement comprendre les notions, mais aussi produire, sous contrainte de temps, une
réponse qui ressemble à ce qu’attendent les corrigés.
11. Les réponses courtes à connaître presque par cœur
Comment des objets communiquent-ils en Java ?
La formulation la plus sûre est la suivante : les classes déclarent des attributs de type référence
sur d’autres objets. Une référence sur un objet permet de lui envoyer un message, c’est-à-dire
d’appeler une méthode correspondant au type de message que l’on veut envoyer. Pour chaque
type de message que l’objet peut recevoir, la classe déclare une méthode associée, et cette
méthode est exécutée quand l’objet reçoit ce message.
Variable de classe, variable d’instance, et mot-clé static
Une variable d’instance appartient à chaque objet séparément : il existe donc une valeur
distincte pour chaque instance. Une variable de classe appartient à la classe elle-même et
n’existe qu’en un seul exemplaire partagé par toutes les instances. En Java, une variable de
classe se déclare avec le mot-clé static ; une variable d’instance est simplement un attribut non
static.
Si trois objets Robot sont créés et qu’un compteur robotCount est incrémenté dans le
constructeur, alors la valeur affichée vaut 3 parce que robotCount est partagé par toute la
classe, et non dupliqué objet par objet.
À quoi sert main, et pourquoi main est-elle statique ?
main est le point d’entrée du programme. La JVM commence par charger une classe puis
appelle sa méthode main. Comme aucun objet particulier de cette classe n’existe encore au
moment du démarrage, la méthode doit être statique afin de pouvoir être appelée sans avoir à
instancier préalablement un objet.
Polymorphisme
Le polymorphisme signifie qu’un même objet peut être manipulé sous plusieurs types
compatibles avec sa position dans l’arbre d’héritage. Un objet de sous-classe possède aussi les
types de ses classes mères et des interfaces qu’il implémente. C’est exactement ce qui permet
Java orienté objet — guide de révision personnalisé
d’écrire par exemple Shape shape = new Circle(...), puis d’appeler [Link]() sans
connaître statiquement plus que le type Shape.
Classe abstraite contre interface
Une interface sert d’abord à déclarer un contrat de méthodes visibles, alors qu’une classe
abstraite peut, en plus, contenir des attributs, des constructeurs, des méthodes déjà
implémentées et éventuellement des méthodes abstraites. La différence à écrire à l’examen
n’est donc pas « modern Java autorise des default methods », mais plutôt : l’interface décrit un
contrat, la classe abstraite peut mutualiser de l’état et du comportement.
Encapsulation : notion, mot-clé, raisons
La notion Java qui permet l’encapsulation est la visibilité des déclarations. Le mot-clé le plus
important ici est private pour les attributs. On encapsule pour contrôler les modifications,
valider les valeurs, garantir la cohérence des données, préserver la cohérence de l’application et
rendre la mise en œuvre interne transparente pour le reste du programme.
La connexion avec le MVC est importante : si les données du modèle sont modifiées via des
mutateurs, ces méthodes peuvent notifier les observateurs ou les vues. Si au contraire d’autres
classes modifient directement les attributs du modèle, la vue risque de ne plus refléter l’état réel
des données.
Transparence de mise en œuvre
La transparence de mise en œuvre signifie qu’on peut modifier la façon dont une classe calcule
ou stocke ses données sans casser le code client, tant que l’interface publique reste la même.
L’exemple typique est celui d’un vecteur : on peut soit mémoriser sa longueur dans un attribut
et la maintenir à jour, soit la recalculer à chaque appel de getLength(). Si les attributs restent
privés et que le monde extérieur passe uniquement par les méthodes publiques, le changement
d’implémentation reste transparent.
Deux usages des interfaces à citer vite et bien
Tu peux presque toujours citer les mêmes usages, parce qu’ils reviennent constamment : outil
de conception, engagement contractuel, outil d’encapsulation, et descriptif de propriétés
communes. Si l’énoncé ne demande que deux usages, l’association la plus simple est souvent «
contrat » et « encapsulation », ou bien « conception » et « contrat ».
Liaison dynamique
La formulation correcte est : la méthode exécutée est choisie au moment de l’exécution en
fonction de la classe d’instanciation réelle de l’objet référencé. Le type déclaré de la variable
suffit à vérifier à la compilation qu’un appel est autorisé, mais le corps de la méthode redéfinie
réellement appelé est déterminé dynamiquement.
12. Comment décider si une expression compile
Les sujets 2024 et 2025 adorent les petites lignes du genre Shape s = new Rectangle(...). Le plus
rentable n’est pas de mémoriser des cas, mais d’appliquer un petit algorithme mental toujours
identique.
Java orienté objet — guide de révision personnalisé
Règle 1 : on ne peut pas instancier ce qui n’est pas concret
new Shape() ne compile pas si Shape est une interface. new AbstractShape(...) ne compile pas si
AbstractShape est abstraite. C’est un piège classique parce que le constructeur peut exister
textuellement dans la classe abstraite, mais l’interdiction d’instanciation reste absolue.
Règle 2 : une référence générale peut viser un objet plus spécifique
Une variable d’un type parent ou d’une interface peut référencer un objet d’une sous-classe
concrète compatible. Donc AbstractShape shape = new Circle(...) compile, et Shape shape = new
Rectangle(...) compile également.
Règle 3 : l’inverse n’est pas vrai sans conversion, et souvent même avec
conversion ce serait faux
Une variable de type Circle ne peut pas recevoir directement un objet de type Shape, parce
qu’un Shape quelconque n’est pas forcément un Circle. De même, Rectangle rect = new Circle(...)
ne compile pas car Circle et Rectangle sont deux sous-classes sœurs, sans relation d’héritage
directe entre elles.
Règle 4 : le type déclaré contrôle les méthodes accessibles
Quand tu écris Shape shape = new Circle(...), tu peux uniquement appeler les méthodes
garanties par Shape. Le fait que l’objet concret soit un Circle ne change pas les droits offerts par
le type déclaré au moment de la compilation. En revanche, cela change la méthode redéfinie qui
sera exécutée à l’exécution.
Application directe au pattern Shape / AbstractShape / Circle / Rectangle
Dans le sujet 2024, les lignes qui compilent sont celles qui montent dans l’arbre de types :
AbstractShape shape = new Circle(); et Shape shape2 = new Rectangle();. Les lignes qui ne
compilent pas sont celles qui tentent d’instancier une abstraction ou de descendre
arbitrairement dans l’arbre : Circle circle = new Shape();, Shape shape1 = new AbstractShape();
et Rectangle rect = new Circle();.
Dans le sujet 2025, le raisonnement est identique malgré le changement de signatures : Shape
shape = new Circle("Circle1", 12) compile, AbstractShape shape2 = new Rectangle("rect1", 12, 15)
compile, tandis que new AbstractShape(), new Shape() et Rectangle rect = new Circle(...) ne
compilent pas. Quand deux sujets successifs recyclent exactement le même mécanisme, c’est un
très bon signal sur ce qu’il faut savoir reconnaître instantanément.
13. Ce que la variable sait, et ce que l’objet fait
Une grande partie des pièges vient de la différence entre type statique et classe d’instanciation.
Le type statique, c’est le type écrit dans le code source à gauche de la variable. Il sert au
compilateur pour vérifier si un appel de méthode a du sens. La classe d’instanciation, c’est la
classe de l’objet réellement créé par new et référencé pendant l’exécution.
On peut donc résumer ainsi : la compilation regarde le type déclaré ; l’exécution regarde la
classe réelle de l’objet. C’est pourquoi une boucle for sur une liste de Shape compile en
Java orienté objet — guide de révision personnalisé
n’utilisant que l’interface commune getArea(), puis appelle tantôt la version Rectangle, tantôt la
version Circle de getArea() au fil des itérations.
Cette distinction est le cœur du triptyque « héritage, polymorphisme, liaison dynamique ». Si tu
maîtrises cette phrase, tu réponds déjà à une fraction énorme des questions de théorie.
14. Ce qui relève de la compilation, et ce qui relève de
l’exécution
À l’examen, beaucoup de copies perdent des points en donnant une erreur d’exécution là où le
programme ne compilerait même pas, ou l’inverse. Il faut donc séparer très nettement les deux
mondes.
Erreurs de compilation typiques
Instancier une interface ou une classe abstraite. Accéder à un attribut private depuis l’extérieur.
Lever une exception vérifiée sans la déclarer dans la signature avec throws ni la traiter. Affecter
un objet à une référence d’un type incompatible. Accéder à un attribut d’instance depuis une
méthode de classe sans passer par un objet.
Erreurs d’exécution typiques
Dereferencer une référence nulle, ce qui produit un NullPointerException. Déclencher une
ArithmeticException pour une division illégale ou une racine carrée d’une valeur négative si le
programmeur le décide explicitement. Échouer à lire un fichier et obtenir une IOException. Plus
généralement : le programme a bien compilé, mais une situation concrète rencontrée à
l’exécution rend l’action impossible.
Quand tu hésites, pose-toi la question suivante : le compilateur peut-il savoir dès maintenant
que quelque chose est impossible ? Si oui, c’est probablement une erreur de compilation. Si tout
dépend de la valeur concrète rencontrée pendant l’exécution, on est du côté des exceptions
d’exécution.
15. Méthode générale pour l’exercice de modélisation
Les deux examens récents finissent par un exercice de conception qui demande moins de
syntaxe parfaite qu’une modélisation propre. La stratégie la plus fiable est toujours la même.
Commencer par le contrat visible
Repère d’abord ce que chaque objet doit savoir faire publiquement. Si l’énoncé dit qu’une
expression doit pouvoir être évaluée et convertie en texte, tu dois immédiatement penser à une
interface ou à une classe abstraite centrale exposant evaluate() et getText(). Si l’énoncé dit
qu’une carte doit permettre de connaître sa puissance, il faut une méthode getPower()
commune à tous les composants.
Repérer l’état partagé, puis l’état spécialisé
Dès que plusieurs sous-classes possèdent les mêmes attributs ou la même logique partielle,
introduis une classe abstraite intermédiaire. C’est exactement le type de factorisation valorisé
Java orienté objet — guide de révision personnalisé
par les corrigés : une classe abstraite pour le socle commun, puis des classes concrètes pour les
cas terminaux.
Séparer feuilles et compositions
Dans un arbre d’objets, certaines classes sont des feuilles, c’est-à-dire des objets simples sans
sous-objets significatifs dans le modèle : par exemple une constante numérique ou un bus.
D’autres sont des compositions : elles contiennent d’autres objets du même domaine, comme
une expression binaire qui possède deux sous-expressions, ou une carte électronique qui
possède un ensemble de composants.
Encapsuler dès le départ
Les attributs doivent partir private, souvent final quand ils sont fixés à la construction. Ensuite,
expose uniquement les accesseurs nécessaires. Dans ces exercices, final est souvent un bon
signal d’intention : la valeur numérique d’une constante, la sous-expression portée par une
expression unaire, le fabriquant d’un composant, ou la collection des observateurs une fois
créée ne sont pas censés être réaffectés sans raison.
Choisir entre héritage et composition
Si l’énoncé signifie « est une sorte de », l’héritage est probablement justifié. Si l’énoncé signifie «
possède un » ou « contient un », il faut préférer la composition. Une expression binaire possède
deux expressions ; une carte possède des composants ; un composant connectable possède des
bus. Ce sont des relations de composition, pas des relations d’héritage.
Ajouter les exceptions seulement quand le domaine l’exige
Si une opération peut échouer pour une raison qui appartient réellement au domaine, il faut le
modéliser. Une racine carrée d’une valeur négative peut conduire à lever une
ArithmeticException. Une lecture de fichier invalide peut conduire à lever une IOException ou
une exception applicative plus précise. En revanche, on n’utilise pas les exceptions pour
remplacer un simple test if là où un contrôle ordinaire suffit.
16. Patron complet pour l’exercice sur les expressions
arithmétiques
Le sujet 2024 est un excellent modèle d’exercice de conception OO. Il demande de représenter
des expressions qui peuvent être soit constantes, soit unaires, soit binaires. La très bonne
architecture consiste à partir d’une abstraction centrale Expression offrant evaluate() et
getText(). Ensuite, on spécialise.
Architecture attendue
La classe ou l’interface Expression est la racine logique du modèle. ConstantExpression
représente une feuille et contient simplement une valeur réelle. UnaryExpression est une classe
abstraite intermédiaire contenant un opérateur et une sous-expression. BinaryExpression est
une autre classe abstraite intermédiaire contenant un opérateur, une expression gauche et une
expression droite. Enfin, les opérations concrètes comme SquareRootExpression,
Java orienté objet — guide de révision personnalisé
AdditionExpression, ProductExpression, SubstractionExpression, DivisionExpression ou
PowerExpression deviennent des sous-classes concrètes spécialisées.
Ce choix est bon parce qu’il factorise ce qui est commun. Toutes les expressions unaires ont une
sous-expression et un opérateur ; toutes les expressions binaires ont deux sous-expressions et
un opérateur. Tu évites ainsi de recopier les mêmes attributs, les mêmes constructeurs et la
même logique de getText() dans chaque sous-classe.
Pourquoi l’évaluation est un cas d’école de composition
Une expression ne se calcule pas en héritant d’une autre expression concrète, mais en
contenant d’autres expressions. L’évaluation d’une expression unaire consiste à évaluer sa sous-
expression puis à appliquer l’opérateur. L’évaluation d’une expression binaire consiste à
évaluer ses deux sous-expressions puis à appliquer l’opérateur aux deux résultats. Cette
récursivité est précisément ce qui rend la modélisation élégante.
Le point précis sur les exceptions
Dans ce modèle, la racine carrée mérite une vigilance particulière. Si la sous-expression s’évalue
à une valeur négative, il est cohérent de lever une ArithmeticException avec un message qui
rappelle l’expression fautive. C’est exactement le genre de détail qui distingue un code
simplement fonctionnel d’une réponse de partiel vraiment propre.
Squelette minimal à avoir en tête
public interface Expression {
double evaluate();
String getText();
public abstract class UnaryExpression implements Expression {
private final String operator;
private final Expression expression;
...
public abstract class BinaryExpression implements Expression {
private final String operator;
private final Expression leftExpression;
private final Expression rightExpression;
...
Java orienté objet — guide de révision personnalisé
Tu n’as pas besoin d’apprendre ce squelette au caractère près. En revanche, il faut retenir la
logique : une abstraction commune, des classes abstraites intermédiaires pour factoriser, puis
des classes concrètes terminales qui redéfinissent evaluate() et parfois getText().
17. Patron complet pour l’exercice sur la carte
électronique
Le sujet 2025 vérifie presque la même compétence, mais sur un domaine différent. Le fond de
l’exercice n’est pas l’électronique : c’est encore la qualité de modélisation. L’idée centrale est
d’avoir un socle Component portant les données communes à tous les composants, puis des
spécialisations propres.
Architecture attendue
Une très bonne réponse consiste à définir une classe abstraite Component avec au minimum un
fabricant, représenté par une chaîne, et une puissance consommée, représentée par un double,
ainsi qu’une méthode getPower(). Bus peut être une sous-classe concrète simple. Comme seuls
certains composants peuvent se connecter à des bus, il est naturel d’introduire une classe
abstraite intermédiaire ConnectableComponent qui hérite de Component et contient un
ensemble de bus. Processor, Memory et Controller héritent alors de ConnectableComponent.
La carte électronique elle-même est aussi un composant, mais un composant particulier : elle
n’est pas connectable à un bus dans le cadre de l’énoncé. Il est donc très cohérent de faire
hériter ElectronicCard de Component directement, avec une collection de Component comme
contenu interne.
Pourquoi getPower() doit être redéfini dans la carte
La carte n’a pas de consommation intrinsèque. Elle consomme la somme des puissances de ses
composants. Cela signifie que la puissance stockée dans la classe mère n’a pas de sens pour elle,
ou bien qu’elle doit être fixée à zéro puis remplacée conceptuellement par une redéfinition de
getPower() qui somme les puissances des composants contenus.
Ce que l’exercice veut vérifier en profondeur
Il veut voir si tu sais distinguer une propriété commune à tous les objets du domaine, une
propriété réservée à certaines sous-classes, et une relation de composition. Il veut aussi voir si
tu sais utiliser une collection d’objets du type parent Component pour agréger des objets plus
spécifiques, ce qui est encore une manifestation du polymorphisme.
Squelette minimal à avoir en tête
public abstract class Component {
private final String manufacturer;
private final double power;
public double getPower() { return power; }
Java orienté objet — guide de révision personnalisé
public abstract class ConnectableComponent extends Component {
private final Set<Bus> buses;
public boolean connect(Bus bus) { ... }
public class ElectronicCard extends Component {
private final Set<Component> components;
@Override public double getPower() { ... }
Là encore, le plus important n’est pas la ponctuation exacte mais la structure conceptuelle : base
commune, spécialisation connectable, composants concrets, carte composite dont la puissance
est calculée par somme.
18. Gestion des exceptions : ce que le sujet 2025 attend
exactement
Le sujet 2025 sur readLines est très révélateur. Il ne veut pas seulement savoir si tu connais try-
catch. Il veut vérifier si tu sais qu’avaler une exception dans une méthode puis retourner null
est souvent une mauvaise idée, parce que le code appelant perd la possibilité de traiter
correctement la cause réelle du problème.
La mauvaise implémentation
Dans la version donnée, la méthode lit le fichier, lève éventuellement une Exception générique
si une ligne a une longueur invalide, capture ensuite toute Exception, affiche la stack trace et
retourne null. Cette stratégie mélange plusieurs problèmes : elle masque la nature précise de
l’erreur, elle consomme l’exception trop tôt, elle force l’appelant à gérer un null ambigu, et elle
emploie une Exception trop générale.
La bonne direction
La bonne solution consiste à laisser remonter les exceptions vers l’appelant en les déclarant
dans la signature avec throws. Pour l’I/O, on déclare IOException. Pour l’erreur métier de
longueur invalide, on définit de préférence une exception applicative dédiée, par exemple
InvalidLineException, qui peut porter des informations utiles comme le numéro de ligne.
Ce qu’il faut penser en termes de responsabilité
La méthode readLines a pour responsabilité de lire et de vérifier. Elle n’a pas pour
responsabilité finale de décider comment l’application entière doit réagir à l’échec. Cette
décision appartient au code appelant, qui dispose d’un contexte plus riche pour choisir entre
afficher un message, journaliser, corriger, abandonner ou demander une nouvelle saisie.
Squelette minimal à avoir en tête
public List<String> readLines(String fileName, double expectedLineLength)
Java orienté objet — guide de révision personnalisé
throws IOException, InvalidLineException {
List<String> lines = [Link]([Link](fileName));
for (int i = 0; i < [Link](); i++) {
String fileLine = [Link](i);
if ([Link]() != expectedLineLength) {
throw new InvalidLineException(..., i);
return lines;
À l’oral comme à l’écrit, la phrase-clé est donc : pour permettre au code appelant de récupérer
l’exception levée et de la traiter correctement, il faut éviter de capturer trop tôt une exception
générale, préférer des exceptions précises, et propager celles qui doivent être gérées plus haut.
19. Dernière checklist anti-piège
Une interface n’est pas instanciable. Une classe abstraite non plus. Une référence d’un type
général peut viser un objet plus spécifique, pas l’inverse. Le type déclaré contrôle les appels
autorisés à la compilation ; la classe réelle de l’objet contrôle la méthode redéfinie appelée à
l’exécution. private sert à encapsuler les attributs ; protected sur les attributs est plutôt
déconseillé. static signifie « lié à la classe ». main est statique parce qu’elle doit être appelée
avant toute instanciation. Les exceptions vérifiées se déclarent avec throws ou se traitent ; les
RuntimeException sont non vérifiées. On n’utilise pas les exceptions pour remplacer un simple
if. En modélisation, on commence par le contrat visible, on factorise ce qui est commun dans
des abstractions, on garde les relations « possède un » sous forme de composition, et on ne
choisit l’héritage que pour les vrais « est une sorte de ».
Java orienté objet — guide de révision personnalisé