- Apprendre
- Informatique et numérique
- Programmation
- Java orienté objet : classes, collections, héritage et tests
- Héritage et polymorphisme en Java
Héritage, interfaces et polymorphisme en Java
extends et super, redéfinis des méthodes héritées et dote tes classes de méthodes toString, equals et hashCode utiles, issues de Object. Définis un comportement partagé avec des classes abstraites et des interfaces, prévois quelle méthode sera exécutée grâce au polymorphisme et à la liaison dynamique, et modélise des options fixes et des données simples avec des types énumérés et des records. Des exemples de code détaillés illustrent chaque étape et les erreurs les plus fréquentes des débutants.DébutantUnité 8 sur 11 de la formation Formation : Java orienté objet : classes, collections, héritage et tests
Ce que tu vas apprendre
- Construis une classe à partir d’une classe existante avec
extends, identifie ce que la nouvelle classe hérite et initialise correctement la partie héritée de chaque objet avecsuper.- Crée une sous-classe avec extendsDéclare une sous-classe, détermine les membres qu’elle hérite et peut utiliser, puis décide à l’aide du test « est un » si l’héritage convient.
- Initialise des objets avec les constructeurs de la superclasseTransmets des valeurs au constructeur de la superclasse avec
super(...), prévois l’ordre d’exécution des constructeurs et évite les erreurs classiques.
- Crée une sous-classe avec extends
- Modifie un comportement hérité en redéfinissant des méthodes, appuie-toi sur la version de la superclasse avec
superet dote tes classes de méthodestoString,equalsethashCodepertinentes, issues deObject.- Redéfinis des méthodes héritéesRemplace ou étends une méthode héritée par une redéfinition correcte, appelle la version de la superclasse avec
superet distingue la redéfinition de la surcharge. - Utilise et redéfinis les méthodes de ObjectExplique pourquoi chaque objet possède
toString,equals,hashCodeetgetClass, comprends leur comportement par défaut et écris une méthodetoStringutile. - Redéfinis correctement equals et hashCodeDéfinis quand deux objets sont égaux avec une méthode
equals(Object)correcte, gardehashCodecohérente avec elle et gère l’égalité dans une hiérarchie de classes.
- Redéfinis des méthodes héritées
- Utilise des classes abstraites pour partager du code entre des classes étroitement apparentées, utilise des interfaces pour garantir un comportement commun à des classes sans lien de parenté et choisis l’outil adapté à ta conception.
- Écris des classes abstraites et des méthodes abstraitesDéclare des classes et des méthodes abstraites, complète-les dans des sous-classes concrètes et utilise une classe abstraite pour partager des champs et du code.
- Définis et implémente des interfacesÉcris une interface comme contrat, implémente-la dans une ou plusieurs classes et combine les interfaces avec l’héritage.
- Ajoute des méthodes par défaut, statiques et privées aux interfacesDote les interfaces d’un comportement partagé avec des méthodes par défaut, ajoute des méthodes auxiliaires statiques et privées et résous les conflits entre méthodes par défaut.
- Choisis entre une classe abstraite et une interfaceCompare les classes abstraites et les interfaces et choisis celle qui convient, ou les deux ensemble, pour une conception donnée.
- Écris des classes abstraites et des méthodes abstraites
- Référence des objets à l’aide de types généraux, prévois quelle méthode sera exécutée, convertis les types sans erreur et écris du code qui continue de fonctionner lorsque de nouvelles sous-classes sont ajoutées.
- Référence des objets avec des types de superclasse et d’interfaceDistingue le type déclaré d’une variable du type effectif de l’objet et utilise des types de superclasse et d’interface pour les variables, les paramètres et les listes.
- Prévois quelle méthode sera exécutée grâce à la liaison dynamiqueSuis les appels de méthodes dans une hiérarchie de classes en distinguant les décisions du compilateur de celles prises à l’exécution.
- Convertis des références entre types avec le transtypage et instanceofEffectue des transtypages descendants sans erreur avec
instanceofet le filtrage par motif, reconnaisClassCastExceptionet identifie quand un test de type révèle l’absence d’une méthode redéfinie. - Conçois du code flexible grâce au polymorphismeÉcris des méthodes et des collections qui utilisent des types généraux afin que de nouvelles classes puissent s’y intégrer sans modification, et rends les classes triables avec
Comparable.
- Référence des objets avec des types de superclasse et d’interface
- Représente un ensemble fixe d’options avec un type énuméré et une donnée simple avec un record, en utilisant les membres que le compilateur génère pour toi.
- Représente des ensembles fixes de valeurs avec des types énumérésDéclare des types énumérés, utilise leurs méthodes intégrées et
switch, et dote leurs constantes de champs, de constructeurs et de comportements. - Modélise des données avec des recordsDéclare des records pour des données simples, utilise le constructeur, les accesseurs,
equals,hashCodeettoStringgénérés, et valide les données dans un constructeur compact.
- Représente des ensembles fixes de valeurs avec des types énumérés
Héritage et polymorphisme en Java : cours
1Étends des classes par héritage
Une sous-classe part des champs et du comportement d’une classe existante, puis ajoute ce qui la distingue. Cette relation détermine à la fois comment les autres parties du code peuvent utiliser l’objet et comment Java le construit. Suis une personne et un étudiant à travers leurs déclarations et leurs constructeurs : les données privées restent sous le contrôle de la superclasse et les appels de constructeurs fournissent les valeurs nécessaires à l’état hérité.
1.1 Construis une sous-classe et protège ses données
L’héritage crée une sous-classe à partir d’une superclasse existante. Dans class Student extends Person, tout étudiant est une personne. Un objet étudiant réunit son état de personne et son état supplémentaire d’étudiant dans un seul objet.
Ajoute les éléments propres à l’étudiant
Voici une petite superclasse et une sous-classe :
class Person {
private String name;
public Person(String name) { this.name = name; }
public String getName() { return name; }
}
class Student extends Person {
private int credits;
public Student(String name) { super(name); }
public void study() { credits += 5; }
public int getCredits() { return credits; }
}
Student hérite de getName(). Elle ajoute un champ de crédits et deux méthodes. Après Student s = new Student("Ada Lovelace");, le champ de crédits vaut initialement 0. Deux appels à s.study() le portent d’abord à 5, puis à 10. s.getName() renvoie toujours « Ada Lovelace » : la méthode héritée lit le champ privé déclaré dans Person.
L’instruction super(name) transmet le nom au constructeur de Person. Les constructeurs ne sont pas hérités, donc Student déclare son propre constructeur. Une sous-classe peut ajouter des méthodes et redéfinir un comportement hérité, mais elle ne peut pas supprimer une méthode publique héritée de son interface de programmation publique.
Identifie les membres accessibles
| Modificateur d’accès | Où le membre est accessible |
|---|---|
public | Toute classe ayant accès au type qui déclare le membre |
protected | Le même paquetage, ainsi que l’accès depuis une sous-classe située dans un autre paquetage |
| Sans modificateur | Le même paquetage |
private | La classe qui déclare le membre |
Les membres privés ne sont pas hérités. Le champ privé name existe toujours dans chaque étudiant, mais Student ne peut pas le lire directement. Garder le champ privé et fournir un accesseur permet à Person de contrôler ses propres données. Entre paquetages, l’accès à un membre d’instance protected comporte une restriction supplémentaire : le code d’une sous-classe accède au membre par lui-même ou par une référence du type de sa propre sous-classe, et non par une référence quelconque à la superclasse.
Teste la relation avant d’étendre une classe
Java autorise une superclasse directe par classe. Une chaîne telle que GraduateStudent extends Student et Student extends Person est valide ; extends Student, Teacher ne l’est pas. Une classe sans superclasse explicite étend Object. Object elle-même n’a pas de superclasse.
Utilise le test « est un » : tout objet de la sous-classe peut-il être utilisé partout où une instance de la superclasse est attendue ? Un camion est un véhicule. Un client possède une adresse, mais un client n’est pas une adresse. Pour cette seconde relation, utilise la composition :
class Customer {
private Address address;
}
Ici, Address est une autre classe. Le champ permet à un client d’utiliser une adresse sans prétendre en être une. Étendre une classe pour simplement emprunter une méthode pratique revient aussi à hériter de tout son contrat public. Enfin, une classe final ne peut pas être étendue. Comme String est finale, class ShortString extends String ne compile pas.
Points clés
- Un objet de sous-classe comprend l’état de la superclasse et tout état ajouté par la sous-classe.
- Les champs privés de la superclasse sont accessibles par les méthodes de celle-ci.
- Une classe étend une superclasse directe ; les constructeurs ne sont pas hérités.
- Utilise l’héritage pour une relation « est un » et la composition pour une relation « possède un ».
- Une classe
finalne peut pas être étendue.
1.2 Transmets des valeurs dans une chaîne de constructeurs
Un constructeur de sous-classe doit assurer la construction de la partie correspondant à la superclasse dans le même objet. Il peut appeler un constructeur de la superclasse avec super(...) ou déléguer à un autre constructeur de sa propre classe avec this(...).
Choisis le constructeur de la superclasse
class Person {
private final String name;
Person(String name) { this.name = name; }
}
class Student extends Person {
private final String studentId;
Student(String name, String studentId) {
super(name);
this.studentId = studentId;
}
Student(String name) {
this(name, "no id yet");
}
}
Le constructeur à deux arguments transmet le nom à Person et stocke lui-même l’identifiant. Le constructeur à un argument lui délègue cette tâche. Un constructeur contient au plus un appel explicite de constructeur : this(...) ou super(...). En suivant la chaîne de this(...), on atteint finalement un constructeur qui appelle super(...).
Si aucun des deux n’est écrit, Java insère super(). Cet appel échoue ici, car Person ne possède qu’un constructeur exigeant un nom. Dès qu’un constructeur est déclaré, Java ne fournit pas de constructeur par défaut sans argument. Une classe sans constructeur déclaré reçoit un constructeur par défaut qui appelle super().
Écrire new Person(name) dans Student produit autre chose : cela construit un objet distinct. Cette instruction n’initialise pas l’état de personne de l’étudiant courant et ne remplace pas l’appel obligatoire au constructeur de la superclasse.
Suis l’ordre de l’initialisation
class Base {
Base() { System.out.println("Base ready"); }
}
class Middle extends Base {
Middle() { System.out.println("Middle ready"); }
}
class Top extends Middle {
Top() { super(); System.out.println("Top ready"); }
}
new Top() remonte la chaîne de constructeurs jusqu’à Object. Au retour des appels, la sortie est « Base ready », « Middle ready », puis « Top ready ». Les initialiseurs de champs de chaque classe s’exécutent après le retour du constructeur de sa superclasse et avant le reste du corps de son constructeur.
Une méthode redéfinissable appelée par un constructeur de superclasse peut déjà exécuter une version redéfinie dans une sous-classe. Si cette version lit un champ de type référence de la sous-classe avant que sa valeur lui soit affectée, elle voit null, même si le champ possède un initialiseur dans sa déclaration. Limite le travail du constructeur à l’initialisation de l’état ; évite les appels aux méthodes que les sous-classes peuvent redéfinir.
Reconnais le changement apporté par Java 25
En Java 25, un constructeur peut valider des paramètres avant son appel explicite de constructeur :
Student(String name, String studentId, int credits) {
if (credits < 0) {
throw new IllegalArgumentException("negative credits");
}
super(name);
this.studentId = studentId;
}
Java 25 autorise aussi, avant l’appel, des affectations simples à des champs déclarés dans la classe courante sans initialiseur dans leur déclaration. Il reste interdit de lire les champs de l’objet courant, d’appeler ses méthodes d’instance ou de transmettre this avant l’appel. Pour les versions standard du langage antérieures à Java 25, garde super(...) ou this(...) en première position. Java 22 à 24 proposaient des formes antérieures en aperçu.
Points clés
super(...)construit la partie de l’objet courant correspondant à la superclasse.- Un
super()implicite nécessite un constructeur sans argument accessible dans la superclasse. this(...)délègue à un constructeur de la même classe ; sa chaîne doit atteindre un constructeur de la superclasse.- Les initialiseurs de champs s’exécutent après le retour du constructeur de la superclasse.
- Java 25 autorise certaines opérations avant un appel explicite de constructeur.
- Une méthode redéfinie appelée pendant la construction peut observer les champs de la sous-classe avant que leurs valeurs leur soient affectées.
2Redéfinis des méthodes et utilise la classe Object
Les méthodes héritées donnent aux objets apparentés un point de départ commun. La redéfinition permet à chaque sous-classe de répondre au même appel à sa manière, tandis que les méthodes héritées de Object déterminent la façon dont un objet s’affiche et se compare aux autres. De petites différences de signature comptent ici : une méthode peut sembler correcte au lecteur sans remplacer celle que le code de bibliothèque appelle réellement. L’égalité impose aussi des engagements sur lesquels reposent les collections fondées sur le hachage.
2.1 Remplace un comportement hérité par une redéfinition
Une redéfinition donne à une sous-classe sa propre implémentation d’une méthode d’instance héritée. Son nom et ses types de paramètres correspondent à ceux de la méthode initiale, de sorte que les appelants peuvent continuer à utiliser la même opération.
Étends une méthode fonctionnelle
Supposons que la rémunération annuelle d’un salarié corresponde à douze mensualités. Un responsable reçoit aussi une prime annuelle :
class Employee {
private final double monthlySalary;
Employee(double monthlySalary) {
this.monthlySalary = monthlySalary;
}
public double yearlyPay() { return 12 * monthlySalary; }
}
class Manager extends Employee {
private final double bonus;
Manager(double monthlySalary, double bonus) {
super(monthlySalary);
this.bonus = bonus;
}
@Override
public double yearlyPay() {
return super.yearlyPay() + bonus;
}
}
Pour new Manager(3000.0, 5000.0), l’appel à la superclasse renvoie 36 000,0. L’ajout de 5 000,0 donne 41 000,0. super.yearlyPay() sélectionne délibérément l’implémentation de la superclasse. La méthode de la superclasse reste accessible dans la sous-classe, même si les appels ordinaires sur le responsable exécutent la version redéfinie.
Écrire return yearlyPay() + bonus; appelle à nouveau la méthode du responsable elle-même. Aucun appel ne se termine, si bien que l’exécution finit par échouer avec StackOverflowError. L’absence de super. change le sens de l’appel.
Laisse le compilateur vérifier la signature
Place @Override au-dessus de chaque méthode qui en redéfinit une autre. Une faute d’orthographe comme yearlypay() ou un paramètre supplémentaire provoque alors une erreur de compilation. Sans l’annotation, Java peut accepter une nouvelle méthode que les appelants n’utilisent jamais.
Une redéfinition valide suit ces règles :
- Ses types de paramètres correspondent exactement.
- Son type de retour est identique ou, pour un retour de type objet, est un sous-type du type initial. Cette restriction autorisée est un type de retour covariant.
- Son accès est au moins aussi ouvert que celui de la méthode héritée.
- Elle n’ajoute pas d’exceptions contrôlées interdites par la déclaration héritée.
Par exemple, public String toString() ne peut pas être remplacée par String toString() avec un accès de paquetage. Les appelants existants doivent conserver l’accès.
Distingue redéfinition, surcharge et masquage
| Déclaration | Effet |
|---|---|
| Même signature de méthode d’instance dans une sous-classe | Redéfinit le comportement hérité |
| Même nom, liste de paramètres différente | Ajoute une surcharge |
| Même signature de méthode statique dans une sous-classe | Masque une méthode statique |
role() et role(String language) sont des surcharges ; la seconde ne remplace pas la première. Le choix d’une surcharge utilise les types déclarés des arguments à la compilation. Les méthodes statiques sont aussi sélectionnées à partir du type utilisé à la compilation ; elles ne sont donc pas redéfinies dynamiquement.
Une méthode privée n’est pas héritée ; une méthode de même nom dans la sous-classe est indépendante. Une méthode final ne peut pas être redéfinie, et les constructeurs ne peuvent pas l’être non plus. Au-delà de la compilation, la méthode doit toujours remplir son objectif initial. Une version redéfinie pour un responsable qui renverrait la rémunération mensuelle fausserait le code qui additionne les rémunérations annuelles, même si sa signature était correcte.
Points clés
- Une méthode redéfinie correspond au nom et aux types de paramètres d’une méthode d’instance héritée.
@Overridedétecte les surcharges accidentelles et les fautes d’orthographe.super.method()réutilise l’implémentation de la superclasse.- Une redéfinition ne peut pas réduire l’accès ni élargir les exceptions contrôlées autorisées.
- La surcharge modifie les paramètres ; les méthodes statiques sont masquées et les méthodes final ne peuvent pas être redéfinies.
2.2 Donne aux objets un texte utile et identifie leur classe
Toute classe Java étend Object directement ou indirectement. Les tableaux sont aussi des objets. Cette racine commune fournit des méthodes comme toString(), equals(Object), hashCode() et getClass(), même lorsqu’une classe n’en déclare aucune.
Remplace la description affichée par défaut
La méthode héritée Object.toString() produit un nom de classe, @ et le code de hachage de l’objet en hexadécimal. Un texte comme « school.Person@1b6d3586 » indique la classe mais ne décrit pas les données de la personne. Le nombre peut changer d’une exécution à l’autre et Java ne garantit pas qu’il s’agisse d’une adresse mémoire.
Une redéfinition utile montre les champs dont les lecteurs ont besoin. Ici, un étudiant a hérité de getName() et stocke un champ credits :
@Override
public String toString() {
return getName() + " (" + credits + " credits)";
}
Pour une étudiante nommée Ada Lovelace avec 10 crédits, System.out.println(s) affiche « Ada Lovelace (10 credits) ». L’appel d’affichage obtient automatiquement le texte de l’objet. La concaténation de chaînes fait de même : "Student: " + s inclut la même description.
Pour une référence nulle, l’affichage et la concaténation produisent « null ». Un appel explicite s.toString() lorsque s est null lève une NullPointerException, car aucun objet ne peut recevoir cet appel.
Si Person.toString() renvoie déjà le nom de la personne, une sous-classe peut s’appuyer sur ce résultat :
@Override
public String toString() {
return super.toString() + ", student id " + studentId;
}
Pour l’identifiant « S-17 », cela produit « Ada Lovelace, student id S-17 ». L’utilité de super.toString() dépend de ce que la superclasse renvoie réellement. Si elle utilise encore la version de Object, elle apporte le texte par défaut contenant la classe et le code de hachage.
Distingue l’identité des informations sur la classe
Object.equals(Object) compare l’identité : elle renvoie vrai lorsque les deux références désignent le même objet. Deux objets new Person("Ada") distincts ne sont pas égaux selon cette implémentation héritée, même si leurs noms correspondent. Des classes comme String, Integer et ArrayList fournissent des versions redéfinies fondées sur la valeur.
getClass() indique la classe effective à l’exécution :
Person p = new Student("Ada Lovelace");
System.out.println(p.getClass().getSimpleName());
Cela affiche « Student » lorsque Student possède le constructeur montré. Le type déclaré de la variable ne modifie pas la classe de l’objet. getClass() est finale et ne peut pas être redéfinie.
hashCode() est utilisée par les collections fondées sur le hachage. Parmi les autres méthodes de Object figurent la méthode protégée clone() et les méthodes de coordination des threads wait(), notify() et notifyAll().
Vérifie l’orthographe exacte
Java distingue toString() de tostring(). Une méthode toString(String prefix) est une surcharge : l’affichage ne l’appelle donc pas. @Override détecte ces deux erreurs. Une représentation textuelle ne définit pas non plus l’égalité : deux objets peuvent afficher le même texte tout en étant considérés comme inégaux.
Points clés
Objectfournit des méthodes communes à tous les objets Java, y compris les tableaux.- L’affichage et la concaténation de chaînes utilisent
toString()et représentent une référence nulle par « null ». Object.equalscompare l’identité tant qu’une classe ne la redéfinit pas.getClass()renvoie la classe effective et est finale.
2.3 Garde l’égalité et le hachage cohérents
Deux objets peuvent représenter la même valeur sans être le même objet. Un catalogue de livres peut considérer deux objets livre comme égaux lorsque leur code de catalogue et leur édition correspondent. Définis cette règle en redéfinissant equals(Object) et garde le hachage cohérent avec elle.
Compare les champs qui définissent la valeur
import java.util.Objects;
final class Book {
private final String code;
private final int edition;
Book(String code, int edition) {
this.code = code;
this.edition = edition;
}
@Override
public boolean equals(Object other) {
if (this == other) return true;
if (!(other instanceof Book b)) return false;
return edition == b.edition && Objects.equals(code, b.code);
}
@Override
public int hashCode() {
return Objects.hash(code, edition);
}
}
Le test d’identité accepte rapidement le même objet. Le test de type rejette un objet sans rapport ou une référence nulle, puis introduit b comme un Book. Les éditions, de type primitif, sont comparées avec == ; Objects.equals compare les codes sans erreur même si un code est nul. Deux livres portant le code « B-1042 » et l’édition 2 sont considérés comme égaux. L’édition 3 constitue une valeur différente.
Le paramètre doit être de type Object. Écrire equals(Book other) ajoute une surcharge et laisse en place la méthode héritée equals(Object). Le code de bibliothèque qui appelle cette signature continuerait à comparer l’identité. @Override fait échouer la déclaration incorrecte à la compilation.
Respecte les engagements de l’égalité
Pour des références non nulles, l’égalité doit être :
- Réflexive : un objet est égal à lui-même.
- Symétrique : inverser les deux objets donne la même réponse.
- Transitive : si un premier objet est égal à un deuxième et que le deuxième est égal à un troisième, le premier est égal au troisième.
- Cohérente : des comparaisons répétées donnent le même résultat tant que les données comparées ne changent pas.
Un objet doit aussi être considéré comme inégal à null. Des objets égaux doivent renvoyer des codes de hachage égaux. Des objets inégaux peuvent partager un code de hachage : une collision de hachage ne prouve pas l’égalité. Calcule le code de hachage à partir des mêmes champs que l’égalité, comme le fait ici Objects.hash(code, edition).
Une collection fondée sur le hachage utilise le code de hachage pour trouver des candidats avant de vérifier leur égalité. Redéfinir uniquement equals peut conduire un ensemble à conserver des livres distincts mais égaux ou empêcher une Map de trouver une clé égale. Modifier un champ utilisé pour le hachage alors que son objet est stocké comme clé peut laisser cet objet rangé sous son ancien code de hachage.
Préserve la symétrie entre sous-classes
Supposons qu’une classe Book extensible accepte tout objet qui vérifie instanceof Book, tandis que Ebook ajoute la taille du fichier au test d’égalité et n’accepte que les livres numériques. Un livre peut accepter un livre numérique de même code et de même édition, mais le livre numérique rejette le livre. La comparaison a perdu sa symétrie.
Une approche impose des classes effectives identiques, en vérifiant l’absence de nullité avant getClass(). Une autre rend l’égalité de la superclasse finale pour que les sous-classes ne puissent pas étendre la règle d’égalité. Une classe de valeur finale, comme ci-dessus, évite entièrement la question de l’égalité dans les sous-classes. Les records génèrent l’égalité et le hachage à partir de leurs composants ; les constantes d’énumération possèdent déjà une égalité fondée sur l’identité, adaptée à leurs valeurs fixes.
Points clés
- Redéfinis
equals(Object), et non une surcharge dont le paramètre est la classe elle-même. - L’égalité doit être réflexive, symétrique, transitive et cohérente ; la comparaison avec null est fausse.
- Des objets égaux doivent avoir des codes de hachage égaux ; un code partagé ne prouve pas l’égalité.
- Garde les champs utilisés pour l’égalité inchangés tant qu’un objet est une clé d’une collection fondée sur le hachage ou un élément d’un ensemble.
- Une superclasse et une sous-classe ne doivent pas utiliser des règles d’égalité qui se contredisent lorsque les opérandes sont inversés.
- Les records et les types énumérés fournissent un comportement d’égalité intégré approprié.
3Définis des contrats avec des classes abstraites et des interfaces
Certaines classes apparentées partagent des champs et des méthodes fonctionnelles ; d’autres classes ne partagent qu’une capacité. Les classes abstraites et les interfaces expriment ces différentes relations. Un programme de dessin peut conserver les données communes des formes dans une classe abstraite, tandis qu’un programme de charge utilise des appareils sans lien de parenté par l’intermédiaire d’une interface. Les deux approches permettent au compilateur de vérifier les comportements requis avant l’exécution du programme, et Java fournit des règles précises lorsque des implémentations héritées sont en concurrence.
3.1 Partage un état et impose un comportement aux sous-classes
Une classe abstraite est une classe de base qui ne peut pas être instanciée directement. Elle peut contenir des champs communs et des méthodes fonctionnelles tout en laissant certaines opérations à compléter par les sous-classes.
Laisse une opération obligatoire abstraite
abstract class Shape {
private final String name;
Shape(String name) { this.name = name; }
public abstract double area();
public String describe() {
return name + " with area " + area();
}
}
class Rectangle extends Shape {
private final double width;
private final double height;
Rectangle(double width, double height) {
super("rectangle");
this.width = width;
this.height = height;
}
@Override
public double area() { return width * height; }
}
area() possède une signature et un point-virgule, mais pas de corps. Elle indique ce que toute forme concrète doit fournir. Comme Shape contient une méthode abstraite, la classe doit aussi être abstraite. Rectangle fournit l’implémentation manquante et peut être instanciée.
Pour new Rectangle(3.0, 4.0), le constructeur de la superclasse stocke « rectangle ». Le constructeur de la sous-classe stocke les dimensions. Son aire est de 12,0 mètres carrés lorsque les dimensions sont en mètres. L’appel de describe() renvoie « rectangle with area 12.0 ». L’appel à area() dans la méthode héritée exécute l’implémentation du rectangle.
Utilise normalement les constructeurs et les méthodes concrètes
Une classe abstraite peut avoir des champs, des constructeurs et des méthodes avec un corps, aux niveaux d’accès habituels. Son constructeur s’exécute lors de la création d’un objet de sous-classe. new Shape() est interdit même si un constructeur correspondant existe ; new Rectangle(3.0, 4.0) est valide.
Une seconde sous-classe peut utiliser la même description héritée tout en calculant l’aire différemment. Une implémentation pour le cercle renvoie Math.PI * radius * radius. Pour un rayon de 1,5 m, le calcul est π multiplié par 2,25, soit environ 7,07 mètres carrés. La méthode partagée describe() n’a pas besoin d’une branche propre au cercle.
Une variable peut tout de même avoir le type abstrait : Shape s = new Rectangle(3.0, 4.0);. Java interdit de construire la classe abstraite, mais autorise à référencer un objet concret par son intermédiaire.
Fais du travail manquant une erreur de compilation
Chaque sous-classe concrète doit fournir ou hériter des implémentations de toutes les méthodes abstraites. Une sous-classe qui en laisse certaines inachevées doit aussi être déclarée abstraite. Renvoyer 0 depuis une méthode provisoire area() permettrait à une sous-classe concrète incomplète de compiler et de donner discrètement des résultats erronés. Déclarer area() abstraite rend ce travail manquant visible au compilateur.
Les méthodes abstraites ne peuvent pas être privées, statiques ou finales : chacun de ces modificateurs empêcherait la redéfinition par la sous-classe qui fournit l’opération. Une classe abstraite peut ne contenir aucune méthode abstraite ; le modificateur empêche alors uniquement sa construction directe. Utilise ce type de classe de base lorsque ses sous-classes passent le test « est un » et bénéficient d’un état ou de code partagé.
Points clés
- Une classe abstraite ne peut pas être construite directement, mais son constructeur s’exécute pour les objets des sous-classes.
- Une méthode abstraite n’a pas de corps et exige une implémentation concrète.
- Une sous-classe concrète doit compléter chaque méthode abstraite héritée.
- Un type abstrait peut servir pour des variables, des paramètres et des listes.
- Les classes abstraites peuvent combiner des champs communs, des méthodes fonctionnelles et des opérations obligatoires.
3.2 Donne un contrat commun à des classes sans lien de parenté
Une interface nomme une capacité que plusieurs classes peuvent fournir. Elle indique les méthodes disponibles pour cette capacité sans exiger que les classes partagent les mêmes champs d’instance ou la même superclasse.
Déclare et implémente les méthodes
interface Chargeable {
int batteryPercent();
void charge(int minutes);
}
class Phone implements Chargeable {
private int battery;
Phone(int battery) { this.battery = battery; }
@Override
public int batteryPercent() { return battery; }
@Override
public void charge(int minutes) {
battery = Math.min(100, battery + 2 * minutes);
}
}
Les méthodes abstraites de l’interface sont implicitement publiques. Les méthodes qui les implémentent doivent être explicitement publiques ; omettre ce modificateur réduirait l’accès et ferait échouer la compilation. Une classe concrète doit fournir ou hériter des implémentations publiques appropriées pour chaque méthode abstraite. Une classe abstraite qui implémente l’interface peut en laisser certaines à ses sous-classes.
Pour un téléphone chargé à 35 %, charge(20) ajoute 40 points de pourcentage et le laisse à 75 %. Après 20 minutes de plus, la charge atteindrait 115 avant plafonnement, donc Math.min stocke 100. Une ElectricScooter peut implémenter la même interface tout en n’ajoutant qu’un point par minute : en partant de 50 %, elle atteint 70 après 20 minutes. L’interface fixe l’opération disponible, tandis que la classe fournit son comportement.
Utilise l’interface comme type
Chargeable device = new Phone(35);
device.charge(20);
System.out.println(device.batteryPercent());
Ce code affiche 75. Un appelant peut utiliser les méthodes de l’interface et les méthodes publiques disponibles sur tout objet. Les opérations propres au téléphone ne sont pas disponibles par l’intermédiaire de device, sauf si l’interface déclarée les expose.
Les interfaces peuvent aussi être les types de paramètres, de valeurs de retour et d’éléments de liste. Une méthode qui accepte Chargeable peut utiliser un téléphone ou une trottinette sans connaître l’organisation de leurs champs. La bibliothèque Java utilise la même approche : List possède des implémentations comme ArrayList et LinkedList ; Runnable déclare run() ; Comparable déclare la comparaison selon l’ordre naturel.
Combine plusieurs capacités
Une classe étend une classe et peut implémenter plusieurs interfaces. L’ordre est class Phone extends Device implements Chargeable, Connectable. Une interface peut en étendre plusieurs : interface SmartDevice extends Chargeable, Connectable. Les classes qui l’implémentent doivent alors fournir toutes les opérations abstraites héritées.
Une interface n’a ni constructeurs ni champs d’instance. new Chargeable() ne compile pas. Un champ déclaré comme int FULL = 100; est implicitement une constante publique, statique et finale, consultée sous la forme Chargeable.FULL.
Une méthode abstraite d’interface n’apporte aucun code fonctionnel. Son implémentation peut provenir de la classe elle-même ou d’une méthode publique héritée de la superclasse. Les méthodes par défaut sont les méthodes d’interface qui peuvent fournir un comportement d’instance hérité.
Points clés
- Une interface est un type qui décrit une capacité partagée.
- Une classe concrète qui implémente une interface doit fournir ou hériter des implémentations publiques appropriées.
- Une classe peut implémenter plusieurs interfaces tout en étendant une classe.
- Les interfaces n’ont ni constructeurs ni champs d’instance ; leurs champs sont des constantes.
- Le type interface permet aux appelants d’utiliser son contrat avec des classes sans lien de parenté.
3.3 Utilise le comportement partagé des interfaces et résous les conflits
Les interfaces peuvent fournir du code fonctionnel ainsi que des déclarations de méthodes abstraites. Le modificateur d’une méthode détermine si les objets qui implémentent l’interface en héritent, si les appelants utilisent le nom de l’interface ou si seul le code de l’interface peut l’utiliser.
Construis un comportement par défaut à partir du contrat
interface Chargeable {
int batteryPercent();
void charge(int minutes);
default boolean needsCharge() {
return batteryPercent() < 20;
}
static int clamp(int percent) {
return Math.max(0, Math.min(100, percent));
}
}
Une méthode par défaut est une méthode d’instance publique qui possède un corps. Un téléphone indiquant 15 % hérite de needsCharge() et renvoie vrai. À 20 %, il renvoie faux. La méthode par défaut appelle l’opération abstraite batteryPercent() ; elle ne lit pas le champ privé d’un téléphone. Une classe peut redéfinir la méthode avec sa propre implémentation publique.
Une méthode statique d’interface appartient à l’interface : Chargeable.clamp(130) renvoie 100 et Chargeable.clamp(-5) renvoie 0. Les classes qui implémentent l’interface n’en héritent pas, donc Phone.clamp(130) n’est pas un appel valide à cette méthode. Les méthodes par défaut et statiques d’interface ont été introduites en Java 8.
Depuis Java 9, une interface peut aussi déclarer des méthodes privées pour partager du code auxiliaire entre ses propres méthodes. Une méthode auxiliaire privée d’instance aide les méthodes par défaut ; une méthode auxiliaire privée statique peut aussi aider les méthodes statiques. Les classes qui implémentent l’interface ne peuvent pas appeler directement ces méthodes auxiliaires.
Résous les conflits entre méthodes par défaut
Deux interfaces sans lien de parenté peuvent fournir la même signature :
interface Lamp {
default String describe() { return "lamp"; }
}
interface Speaker {
default String describe() { return "speaker"; }
}
class SmartLamp implements Lamp, Speaker {
@Override
public String describe() {
return Lamp.super.describe() + " and "
+ Speaker.super.describe();
}
}
Sans la redéfinition, SmartLamp présente un conflit et ne compile pas. Les appels qualifiés InterfaceName.super.method() sélectionnent les méthodes par défaut de ses superinterfaces directes. Cette implémentation renvoie « lamp and speaker ».
| Implémentations en concurrence | Règle |
|---|---|
| Méthode concrète publique héritée de la superclasse et méthode par défaut d’une interface | La méthode de la classe l’emporte |
| Méthodes par défaut d’une interface et de l’interface qui l’étend | L’interface la plus spécifique l’emporte |
| Méthodes par défaut d’interfaces sans lien de parenté | La classe doit résoudre le conflit |
Une déclaration abstraite dans la superclasse peut aussi obliger une sous-classe concrète à implémenter la méthode ; une méthode par défaut d’interface ne supprime pas cette obligation.
Tiens compte des classes qui implémentent déjà l’interface
L’ajout d’une méthode abstraite oblige chaque classe concrète qui implémente l’interface à fournir ou à hériter une implémentation correspondante. Une méthode par défaut évite souvent ce changement en fournissant un comportement fonctionnel, mais elle peut introduire un conflit avec une autre méthode héritée. Les méthodes par défaut aident une interface à évoluer ; les règles d’héritage déterminent toujours si les classes obtenues compilent.
Points clés
- Une méthode par défaut fournit un comportement d’instance public qui peut être redéfini.
- Appelle une méthode statique d’interface à l’aide du nom de l’interface.
- Les méthodes privées d’interface sont des méthodes auxiliaires visibles uniquement dans l’interface.
- Une méthode concrète de classe est prioritaire sur une méthode par défaut d’interface.
- Des méthodes par défaut concurrentes issues d’interfaces sans lien de parenté exigent une redéfinition ; dans l’autre cas, la méthode par défaut de l’interface la plus spécifique l’emporte.
- L’ajout d’une méthode par défaut peut introduire des conflits entre méthodes héritées.
3.4 Choisis une base commune, une capacité ou les deux
Une classe abstraite et une interface peuvent toutes deux définir des méthodes obligatoires. Le choix dépend de la relation entre les objets et de la nécessité pour leur implémentation de partager un état d’instance.
Compare ce que chacune fournit
| Question | Classe abstraite | Interface |
|---|---|---|
| Peut-elle stocker des champs propres à chaque objet ? | Oui | Non |
| Peut-elle avoir des constructeurs ? | Oui | Non |
| Peut-elle fournir des méthodes d’instance fonctionnelles ? | Oui | Oui, avec des méthodes par défaut |
| Combien une classe peut-elle en utiliser directement ? | Une superclasse | Plusieurs interfaces implémentées |
| Quels niveaux d’accès sont disponibles ? | Tous les niveaux habituels | Méthodes publiques et méthodes auxiliaires privées |
Les champs d’une interface sont des constantes publiques, statiques et finales, et non des données propres à chaque objet. Les méthodes auxiliaires privées d’une interface aident ses propres méthodes fonctionnelles ; elles ne créent pas d’état privé pour chaque objet qui l’implémente.
Place l’état de classes étroitement apparentées dans une classe abstraite
Un programme de dessin comporte des cercles, des rectangles et des triangles. Chaque forme stocke un nom et une position, et calcule son aire différemment. Une classe abstraite Shape peut conserver les champs et le constructeur communs, fournir des méthodes fonctionnelles de déplacement ou de description et déclarer area() abstraite.
Toutes les formes concrètes sont des sortes de Shape, si bien que le reste du code peut demander une forme et recevoir n’importe laquelle. De même, une classe abstraite Enemy pourrait posséder des points de vie et une méthode partagée takeDamage(int points), tout en laissant les classes propres à chaque ennemi définir le déplacement. L’état partagé donne à ces classes de base un rôle utile.
Place une capacité transversale dans une interface
Le même programme de dessin peut exporter des formes, des zones de texte et des images. L’export ne fait pas d’une zone de texte une forme, et ces objets peuvent déjà avoir besoin de superclasses différentes. Une interface Exportable peut déclarer String exportText(). Chaque classe implémente l’opération à partir de ses propres données.
Une classe peut combiner les deux rôles : class Circle extends Shape implements Exportable. La superclasse fournit l’état de la forme et le code partagé ; l’interface permet au code d’export d’utiliser le cercle. Une classe qui implémente plusieurs interfaces garantit plusieurs capacités sans hériter de leurs champs d’instance.
Combine un contrat et une implémentation partielle
Une classe abstraite peut implémenter une interface et ne compléter que certaines méthodes. Les sous-classes concrètes terminent les opérations restantes. List et AbstractList dans Java utilisent cette organisation ; ArrayList étend AbstractList. Les appelants peuvent dépendre de l’interface tandis que les classes qui l’implémentent réutilisent une classe de base lorsqu’elle convient.
Par exemple, List<String> names = new ArrayList<>(); permet aux appelants d’ajouter, de lire et de supprimer des noms par l’intermédiaire de List. Remplacer l’initialiseur par new LinkedList<>() préserve ces appels. Un paramètre déclaré comme ArrayList<String> refuserait une LinkedList<String> même s’il n’a besoin que d’opérations de liste.
Une classe abstraite ne contenant que des méthodes abstraites occupe tout de même l’unique place de superclasse de la sous-classe. Elle n’est pas interchangeable avec une interface. Choisis l’interface lorsque seul le contrat commun est nécessaire et ajoute une classe de base lorsque des implémentations apparentées bénéficient d’un état ou de code partagé.
Points clés
- Utilise une classe abstraite pour des types d’objets apparentés qui partagent un état ou du code.
- Utilise une interface pour une capacité que des classes sans lien de parenté peuvent fournir.
- Une classe peut à la fois étendre une classe de base abstraite et implémenter des interfaces.
- Une implémentation abstraite partielle peut prendre en charge une interface sans limiter les appelants à cette classe de base.
4Écris des programmes flexibles grâce au polymorphisme
Le type d’une variable et la classe d’un objet répondent à des questions différentes. Le type déclaré de la variable détermine les appels qui compilent ; la classe effective de l’objet détermine la version redéfinie qui s’exécute. Distinguer ces décisions permet de suivre l’exécution d’un code inconnu, d’utiliser les transtypages sans erreur et d’écrire des boucles qui fonctionnent avec de nouvelles implémentations. La même approche s’applique aux interfaces de bibliothèque comme Comparable, qui donne aux objets un ordre naturel de tri.
4.1 Distingue le type de la référence de la classe de l’objet
Le polymorphisme permet à une référence de type général de désigner des objets de différents sous-types. L’objet conserve tout son état et son comportement ; la référence donne aux appelants une vue particulière des opérations disponibles.
Nomme les deux types
Person p = new Student("Ada Lovelace", "S-17");
Le type déclaré, aussi appelé type statique, est Person : il apparaît dans la déclaration de la variable. Le type effectif, ou type à l’exécution, est Student : c’est la classe instanciée par new. La classe effective de l’objet ne change pas après sa construction.
Affecter une référence d’étudiant à une référence de personne est un transtypage ascendant. Il est automatique et sûr, car tout étudiant est une personne. Le même étudiant peut aussi être référencé par une variable Object. Une référence à un objet qui implémente une interface peut faire l’objet d’un transtypage ascendant vers cette interface, comme dans Chargeable c = new Phone(35);.
Aucune donnée de l’étudiant n’est supprimée par l’affectation. Il existe toujours un seul objet étudiant avec son identifiant d’étudiant, les données de personne héritées et ses méthodes redéfinies. Le fait de le référencer par un type plus général change ce à quoi le compilateur permet d’accéder par cette référence.
Vérifie les appels par rapport au type déclaré
Si Person déclare getName(), alors p.getName() compile. Si seule Student déclare getStudentId(), alors p.getStudentId() ne compile pas. Le compilateur vérifie l’interface de programmation de Person même lorsque l’initialiseur crée clairement un étudiant.
L’affectation inverse Student s = p; n’est pas acceptée sans transtypage. Une référence de personne pourrait désigner une personne qui n’est pas étudiante. Cette restriction empêche le code de traiter un objet comme s’il appartenait à un sous-type dont il n’est pas une instance.
| Type déclaré | Appels disponibles par la référence |
|---|---|
Object | Opérations publiques fournies par Object |
Person | Opérations accessibles de Person, y compris celles qui sont héritées |
Student | Opérations accessibles propres à l’étudiant et opérations héritées |
Permets aux paramètres et aux listes d’accepter plusieurs sous-types
Une méthode static void printCard(Person person) peut recevoir un objet Person, Student ou Teacher, à condition que les deux dernières classes étendent Person. Son code peut s’appuyer sur le contrat de personne. Un type de retour peut aussi être général, par exemple pour une méthode qui renvoie un Shape.
Une liste dont le type d’élément est général peut stocker différentes classes de formes :
List<Shape> shapes = new ArrayList<>();
shapes.add(new Circle(1.5));
shapes.add(new Rectangle(3.0, 4.0));
for (Shape shape : shapes) {
System.out.println(shape.area());
}
Ici, List et ArrayList proviennent de java.util, et chaque forme concrète fournit area(). La boucle utilise uniquement cette opération commune.
Une List<Circle> n’est pas une List<Shape>. Si c’était le cas, du code qui la reçoit comme liste de formes pourrait ajouter un rectangle dans une liste censée ne contenir que des cercles. Lorsqu’une méthode exige une List<Shape>, crée la liste avec ce type d’élément dès le départ.
Points clés
- Le type déclaré détermine les opérations auxquelles une référence peut accéder.
- Le type effectif est la classe de l’objet et ne change pas après sa construction.
- Le transtypage ascendant vers une superclasse ou une interface implémentée est automatique et sûr.
- Un type général pour un paramètre ou un élément de liste peut prendre en charge plusieurs sous-types concrets.
- Une
List<Circle>ne peut pas être utilisée comme uneList<Shape>.
4.2 Suis la sélection des surcharges et la liaison dynamique
Un appel de méthode est résolu en deux étapes. À la compilation, Java vérifie les types déclarés et choisit une signature de méthode. À l’exécution, la liaison dynamique choisit l’implémentation redéfinie de la méthode d’instance selon la classe effective de l’objet.
Suis un appel dans une méthode héritée
class A {
String who() { return "A"; }
String hello() { return "Hello from " + who(); }
}
class B extends A {
@Override String who() { return "B"; }
}
class C extends B { }
Avec A x = new C();, x.hello() est autorisé car A déclare hello(). La classe C ne redéfinit pas hello(), et B non plus, donc Java exécute l’implémentation de A.
Dans cette méthode, who() signifie this.who(). L’objet receveur reste l’objet C. Java cherche à partir de C, puis trouve l’implémentation de who() la plus proche dans B. Le résultat est « Hello from B ». Entrer dans une méthode héritée ne transforme pas l’objet receveur en objet de la superclasse.
Choisis une surcharge avant une redéfinition
static String kind(Object value) { return "object"; }
static String kind(String value) { return "string"; }
Dans une classe qui contient ces méthodes, Object value = "hi"; kind(value) sélectionne kind(Object) et renvoie « object ». Le type déclaré de la variable est Object ; Java ne réexamine pas les surcharges à partir de la chaîne effective à l’exécution.
Pour un appel de méthode d’instance, la liaison dynamique peut encore choisir une redéfinition de la signature déjà sélectionnée. Elle ne peut pas passer à une autre surcharge qui semblerait simplement plus spécifique pour l’argument effectif. Identifie d’abord la signature, puis cherche sa redéfinition.
Reconnais les appels et les membres qui ne suivent pas ce mécanisme
| Membre ou appel | Ce qui le sélectionne |
|---|---|
| Méthode d’instance redéfinie | Classe effective de l’objet receveur, après sélection de la signature |
| Méthode statique | Type utilisé à la compilation pour l’appel |
| Champ masqué | Type déclaré de la référence |
| Méthode privée | Code de la classe qui la déclare |
super.method() | Implémentation de la superclasse |
Si P déclare String label = "P"; et que Q extends P déclare son propre String label = "Q";, alors P p = new Q(); p.label lit « P ». Les champs sont masqués, et non redéfinis. Les deux champs existent dans l’objet. Évite de réutiliser les noms des champs hérités, car le type de référence peut amener des accès d’apparence identique à lire des états différents.
super.method() sélectionne l’implémentation de la superclasse, y compris une implémentation dont cette superclasse hérite. Si la méthode sélectionnée effectue elle-même un appel ordinaire sur this, cet appel interne peut encore être lié à une méthode redéfinie dans une sous-classe.
Points clés
- La compilation fixe la signature qui peut être appelée, y compris le choix de la surcharge.
- La liaison dynamique sélectionne la redéfinition la plus proche de cette signature en remontant depuis la classe effective.
- Un appel ordinaire dans une méthode héritée utilise toujours l’objet receveur effectif.
- Les champs, les méthodes statiques, les méthodes privées et les appels explicites avec
supern’utilisent pas la liaison dynamique habituelle des méthodes redéfinies.
4.3 Vérifie un sous-type avant d’utiliser ses méthodes particulières
Une référence de type général doit parfois accéder à une opération propre à un seul sous-type. Un transtypage descendant indique au compilateur de traiter la référence comme ce sous-type. À l’exécution, Java vérifie si l’objet est déjà de ce type.
Comprends ce que change un transtypage
Supposons que Person p référence un étudiant. Student s = (Student) p; permet d’appeler s.getStudentId(). Les deux variables référencent toujours le même objet. Le transtypage ne le copie pas, ne change pas sa classe effective et n’ajoute pas de champs d’étudiant à une simple personne.
Si p référence en réalité new Person("Grace Hopper"), ce transtypage lève une ClassCastException. Le compilateur accepte la relation possible entre personne et étudiant, mais l’objet à l’exécution doit effectivement la respecter. Un transtypage entre des classes qui ne peuvent pas être apparentées, comme une référence Person transtypée vers la classe finale Integer, échoue à la compilation.
Cela diffère d’une conversion primitive : (int) 3.9 crée la valeur entière 3. Un transtypage de référence vérifie et change seulement le type avec lequel une référence existante est utilisée.
Combine le test et la nouvelle variable
Depuis Java 16, le filtrage par motif rend le cas sûr plus concis :
static void showStudentId(Person person) {
if (person instanceof Student student) {
System.out.println(student.getStudentId());
}
}
Le test est vrai pour un étudiant ou une instance d’une sous-classe d’étudiant. La variable student est disponible dans le bloc, où l’on sait que le test a réussi. Une simple personne passe le bloc. Une référence nulle rend aussi le test faux, si bien que le bloc ne tente jamais d’appeler une méthode sur null.
L’ancienne forme teste person instanceof Student, puis déclare Student student = (Student) person; dans la branche exécutée lorsque le test réussit. Les deux formes protègent le transtypage. Tester ailleurs un objet sans rapport ne protège pas un transtypage de person ; le test et le transtypage doivent concerner la même référence.
Un transtypage ascendant n’a pas besoin d’être explicite. Person person = student; compile déjà et ne peut pas échouer à cause d’une incompatibilité de type. Le transtypage de null vers un type référence compatible produit null ; utiliser ce résultat pour appeler une méthode d’instance échoue néanmoins avec une NullPointerException.
Décide si un test de type est nécessaire
Les tests de type conviennent au code qui reçoit un Object, comme equals(Object other), ou qui a besoin d’une opération propre à un sous-type, comme l’identifiant d’un étudiant. Ils peuvent aussi aider à manipuler des classes qui ne peuvent pas être modifiées.
Un enchaînement répété de tests de type pour une opération partagée pose un autre problème. Si le code vérifie chaque classe de forme pour calculer son aire, chaque forme ajoutée exige une branche supplémentaire. Déclarer area() dans Shape et laisser les sous-classes l’implémenter permet à l’appelant d’utiliser simplement shape.area().
Un transtypage ne peut pas rendre une opération commune manquante disponible dans tous les sous-types. Place le comportement partagé dans le contrat commun et réserve les tests de sous-type aux traitements qui exigent réellement ce sous-type particulier.
Points clés
- Un transtypage descendant vérifie un objet existant ; il ne le transforme pas.
- Un transtypage possible peut compiler tout en levant une
ClassCastExceptionà l’exécution. instanceofvaut faux pour null et vrai pour les instances du type ou de ses sous-types.- Un motif
instanceofintroduit une variable correctement typée là où le test est nécessairement vrai. - Les opérations partagées appartiennent au type commun ; les opérations propres à un sous-type peuvent nécessiter un test de type.
4.4 Écris des boucles générales et définis un ordre naturel
Une méthode reste utile avec de nouvelles classes lorsque son paramètre expose précisément les opérations dont elle a besoin. L’appelant fournit les objets ; la liaison dynamique fournit leurs comportements respectifs.
Additionne les aires sans branches propres aux classes
static double totalArea(List<Shape> shapes) {
double total = 0;
for (Shape shape : shapes) {
total += shape.area();
}
return total;
}
Avec un cercle de rayon 1,5 m, un rectangle de dimensions 3,0 m et 4,0 m, et un triangle de base 4,0 m et de hauteur 2,5 m, les aires sont environ 7,06858, 12,0 et 5,0 mètres carrés. En additionnant l’aire non arrondie du cercle, on obtient environ 24,07 mètres carrés. Chaque objet fournit sa propre méthode area().
Ajouter une classe Triangle n’exige pas de modifier la boucle. La nouvelle classe implémente l’opération de forme existante. Un résultat négatif violerait le contrat de l’aire même si la redéfinition compilait ; le code général dépend aussi bien du sens de l’opération que de sa signature.
La même approche fonctionne avec une interface. Une méthode chargeAll(List<Chargeable> devices, int minutes) parcourt les appareils et appelle device.charge(minutes). Après 20 minutes, le téléphone initialement à 35 % atteint 75 % et la trottinette initialement à 50 % atteint 70 %. La boucle ne choisit jamais elle-même une règle de charge.
Laisse les appelants choisir une implémentation de liste
Un paramètre List<Shape> accepte aussi bien une ArrayList<Shape> qu’une LinkedList<Shape>. Un paramètre ArrayList<Shape> refuse la liste chaînée bien qu’il n’ait besoin que d’opérations de liste. Utilise le type le plus général qui fournit les méthodes nécessaires ; tout déclarer comme Object serait trop général, car ce type ne possède ni area() ni les opérations de liste.
Rends les objets triables avec Comparable
Comparable<T> définit l’ordre naturel d’un objet. Le résultat de compareTo est négatif lorsque cet objet précède l’autre, nul en cas d’égalité de rang et positif lorsqu’il le suit. C’est le signe du résultat qui compte ; il n’a pas besoin d’être exactement −1 ou 1.
record Runner(String name, int seconds)
implements Comparable<Runner> {
@Override
public int compareTo(Runner other) {
return Integer.compare(seconds, other.seconds);
}
}
Une liste modifiable contenant Mia en 3 605 s, Ken en 3 490 s et Ana en 3 720 s devient Ken, Mia, Ana après Collections.sort(runners). list.sort(null) utilise aussi l’ordre naturel. Construis une liste modifiable avec new ArrayList<>(List.of(...)) ; tenter de trier le résultat non modifiable de List.of(...) lève une UnsupportedOperationException.
Utilise Integer.compare au lieu de soustraire les valeurs. La soustraction peut provoquer un dépassement de capacité pour de grandes valeurs de signes opposés et renvoyer un signe incorrect. Le même principe s’applique à Double.compare pour les champs décimaux. Dans cet exemple de coureurs, des temps égaux donnent une égalité de rang même si les noms diffèrent : une égalité de comparaison et l’égalité des objets ne signifient donc pas nécessairement la même chose. Privilégie un ordre naturel dont le résultat nul est cohérent avec l’égalité ; documente toute différence intentionnelle.
Points clés
- Le code général appelle une opération partagée et laisse chaque objet fournir son implémentation.
- Un paramètre de type interface peut accepter différentes implémentations d’un même contrat.
compareToutilise un résultat négatif, nul ou positif pour exprimer l’ordre.Integer.compareévite le risque de dépassement de capacité lié à la soustraction de champs entiers.- Le tri nécessite une liste modifiable ;
List.of(...)seule produit une liste non modifiable. - Une égalité de rang peut différer d’une égalité de valeur lorsque la comparaison utilise moins de champs.
5Modélise des valeurs fixes et des données avec des types énumérés et des records
Certains programmes ont besoin d’un ensemble fixe de choix, tandis que d’autres ont besoin d’un petit groupe de valeurs qui vont ensemble. Les types énumérés donnent aux choix un type qui leur est propre ; les records fournissent une grande partie du code habituel des objets de données. Tous deux s’intègrent au système de types de Java et peuvent implémenter des interfaces. Leur comportement intégré économise du travail, mais le choix entre eux et une classe ordinaire dépend toujours de ce qui peut changer et de ce qui rend deux valeurs égales.
5.1 Représente des choix fixes avec un type énuméré
Un type énuméré donne son propre type à un ensemble fixe de choix nommés. enum Size { SMALL, MEDIUM, LARGE } permet d’écrire Size size = Size.MEDIUM;. Il empêche un appelant de transmettre la chaîne mal orthographiée « MEDUIM » ou l’entier 7 à une méthode qui exige un Size.
Utilise les noms, les constantes et switch
Chaque constante d’énumération est un objet unique, donc size == Size.LARGE est une comparaison appropriée. Une variable de type énuméré peut aussi valoir null. Size.values() renvoie toutes les constantes dans l’ordre de leur déclaration ; Size.valueOf("LARGE") trouve le nom exact. La recherche est sensible à la casse : « large » provoque une IllegalArgumentException.
name() renvoie le nom déclaré. ordinal() renvoie le rang, qui commence à zéro, donc Size.MEDIUM.ordinal() vaut 1. Insérer une constante modifie les rangs suivants : un rang ordinal constitue donc un mauvais code à stocker ou une mauvaise valeur métier. Par défaut, toString() renvoie aussi le nom.
Une expression switch, standard depuis Java 14, peut renvoyer une valeur :
String label = switch (size) {
case SMALL -> "S";
case MEDIUM -> "M";
case LARGE -> "L";
};
Nommer chaque constante rend l’expression exhaustive sans branche par défaut. Ces cas supposent que size n’est pas null ; énumérer les constantes ne traite pas le cas null.
Donne à chaque constante des données utiles
enum CupSize {
SMALL(250), MEDIUM(350), LARGE(500);
private final int milliliters;
CupSize(int milliliters) { this.milliliters = milliliters; }
public int milliliters() { return milliliters; }
}
Le point-virgule sépare la liste des constantes des membres. Le constructeur s’exécute une fois pour chaque constante lors de l’initialisation du type énuméré. Il est implicitement privé et les appelants ne peuvent pas créer de nouvelles constantes avec new CupSize(400).
Deux grands gobelets et un petit contiennent 1 250 mL, soit 1,25 L. Ce calcul utilise le champ milliliters, et non les rangs des constantes. Parcourir CupSize.values() et afficher chaque constante et sa capacité donne « SMALL: 250 mL », « MEDIUM: 350 mL » et « LARGE: 500 mL ».
Donne aux constantes leur propre comportement
Un type énuméré peut implémenter une interface et donner aux différentes constantes des corps de méthode distincts :
enum Operation {
ADD { int apply(int a, int b) { return a + b; } },
MULTIPLY { int apply(int a, int b) { return a * b; } };
abstract int apply(int a, int b);
}
Operation.MULTIPLY.apply(6, 7) renvoie 42. Chaque constante complète l’opération abstraite. Les types énumérés étendent implicitement java.lang.Enum ; ils ne peuvent pas étendre une autre classe et les classes ordinaires ne peuvent pas les étendre.
Utilise un type énuméré pour des options fixées lors de l’écriture du programme, comme les directions ou les états d’une commande. La liste changeante des produits d’un magasin relève des données : ajouter des produits ne devrait pas exiger de réécrire et de recompiler une déclaration de type énuméré.
Points clés
- Un type énuméré donne un type distinct à un ensemble fixe de constantes.
values()respecte l’ordre de déclaration etvalueOfrecherche le nom exact.- Stocke les nombres utiles dans des champs, car les rangs ordinaux peuvent changer.
- Les constructeurs de types énumérés initialisent les constantes ; les appelants ne peuvent pas utiliser
newpour en ajouter. - Les types énumérés peuvent implémenter des interfaces et donner aux constantes des comportements différents.
5.2 Porte des valeurs avec un record et valide-les
Un record est une classe de données concise, standard depuis Java 16. Son en-tête énumère les valeurs, appelées composants, qui définissent chaque instance : record Point(int x, int y) { }.
Utilise les membres générés
Le compilateur fournit un champ privé et final pour chaque composant, un constructeur canonique prenant ces composants dans l’ordre et des accesseurs publics portant leurs noms. Il fournit aussi equals, hashCode et toString fondés sur les composants.
Point a = new Point(3, 4);
Point b = new Point(3, 4);
System.out.println(a.x());
System.out.println(a.equals(b));
System.out.println(a == b);
Ce code affiche 3, true et false sur des lignes distinctes. Les valeurs correspondantes des composants rendent les records égaux, mais les deux expressions new créent des objets distincts. Des records égaux ont des codes de hachage égaux. Leur texte généré, utile, comprend les noms du record et des composants ; les JDK actuels affichent « Point[x=3, y=4] ». Ce format textuel exact n’est pas garanti : ne l’analyse donc pas comme un format de données.
Les accesseurs sont x() et y(), et non getX() et getY(). Un mutateur qui effectue l’affectation this.x = x ne compile pas, car le champ du composant est final. Une méthode peut renvoyer une nouvelle valeur à la place : Point moveRight(int dx) { return new Point(x + dx, y); }.
Valide les données dans un constructeur compact
record Temperature(double celsius) {
Temperature {
if (celsius < -273.15) {
throw new IllegalArgumentException("below absolute zero");
}
}
}
Un constructeur compact répète le nom du record sans liste de paramètres. Il vérifie ou normalise les paramètres reçus ; Java affecte les champs des composants après l’exécution de son corps. Cette vérification rejette −300 °C et accepte 21,5 °C. Un composant textuel pourrait être normalisé avec name = name.trim(); avant l’affectation automatique.
Un record public nécessite un constructeur compact public. Les records peuvent aussi déclarer des champs statiques, des méthodes statiques et des méthodes d’instance. Un constructeur supplémentaire délègue au constructeur canonique avec this(...). Les champs d’instance supplémentaires en dehors des composants sont interdits.
Protège les objets mutables des composants
Les champs de composants finaux rendent l’immuabilité superficielle. Un record contenant une liste mutable référence toujours cette liste :
record Team(String name, List<String> members) {
Team {
members = List.copyOf(members);
}
}
Une fois java.util.List importée, ce code stocke une copie non modifiable. Les ajouts ultérieurs à la liste initiale ne modifient pas les membres de l’équipe, et team.members().add("Lee") lève une UnsupportedOperationException. La copie protège la structure de la liste ; elle ne rend pas profondément immuables les objets mutables qui en sont les éléments.
Les records étendent implicitement java.lang.Record et sont finaux. Ils ne peuvent pas étendre une autre classe ni être étendus, mais peuvent implémenter des interfaces comme Comparable. Utilise-les pour des données définies par leurs valeurs ; utilise une classe ordinaire pour un état changeant, des champs d’instance masqués supplémentaires ou une hiérarchie de classes extensible.
Points clés
- L’en-tête d’un record définit les composants ainsi que le constructeur, les accesseurs et les méthodes de valeur générés.
- Les accesseurs d’un record utilisent les noms des composants, comme
x(). - Un constructeur compact vérifie ou normalise les paramètres avant l’affectation automatique des champs.
- Les références des composants d’un record sont finales, mais les objets référencés peuvent rester mutables.
- Une copie de liste non modifiable protège la structure de la liste d’un record.
- Un record est final, ne peut pas étendre explicitement une classe et peut implémenter des interfaces.
Héritage et polymorphisme en Java : exemples
1Construis pas à pas un appareil par héritage
12 étapes
- 1IntroductionUn objet avec un rôle général et un rôle spécifique
Un capteur est une sorte d’appareil. Tout appareil a besoin d’un libellé, tandis qu’un capteur a aussi besoin d’une unité de mesure. L’héritage permet à l’appareil de posséder son libellé et au capteur d’ajouter son unité.
Nous allons construire cette relation, utiliser une méthode héritée et suivre la façon dont les appels de constructeurs initialisent chaque partie. La question essentielle est de savoir quelle classe possède chaque champ et donc quel constructeur doit lui affecter une valeur.
- 2ExempleCommence par l’état nécessaire à tout appareil
class Device { private final String label; Device(String label) { this.label = label; } public String label() { return label; } }Devicecontrôle son libellé privé. Le reste du code le lit aveclabel(). Une sous-classe héritera de cette méthode publique, mais elle ne pourra pas affecter directement une valeur au champ privé. L’initialisation du libellé reste ainsi à un seul endroit. - 3ExempleAjoute uniquement l’état propre au capteur
class Sensor extends Device { private final String unit; Sensor(String label, String unit) { super(label); this.unit = unit; } public String unit() { return unit; } }Sensorétend une classe et ajoute un champ. Son appelsuper(label)confie la responsabilité du libellé àDevice; le constructeur du capteur stocke lui-même l’unité. - 4Point cléLis les deux parties par le capteur
Après
Sensor s = new Sensor("Room", "C");,s.label()renvoie « Room » ets.unit()renvoie « C ». La première méthode est héritée ; la seconde appartient àSensor.Les deux appels concernent le même objet. Étendre
Devicen’a pas créé un second objet appareil à côté du capteur. Le libellé privé est présent, même si le code du capteur y accède par une méthode. - 5DéfiChoisis la relation pour un laboratoire
Un
Labcontient plusieurs capteurs.Labdevrait-il étendreSensorou conserver des références à des capteurs ? Explique la relation.---reveal---
Conserve les références aux capteurs dans des champs ou une collection. Un laboratoire possède des capteurs mais n’est pas lui-même un capteur : la composition convient donc. Étendre
Sensorpromettrait qu’un laboratoire peut être utilisé partout où un capteur est requis, y compris pour des opérations qui décrivent l’unité d’un capteur. - 6ExplicationEntre dans le constructeur de la superclasse
L’appel
new Sensor("Room", "C")sélectionne le constructeur de capteur à deux arguments. Sonsuper(label)appelleDevice(String)pour ce même objet.Deviceatteint d’abord sa propre superclasse,Object, puis affecte le libellé. Au retour deDevice, l’initialisation des champs du capteur et le reste du corps de son constructeur s’exécutent. Enfin, la nouvelle référence devient disponible pour l’appelant. - 7ConseilTrouve le constructeur sans argument manquant
Supprime
super(label)du constructeur du capteur et Java insèresuper(). Cet appel n’a pas de correspondant :Devicene déclare queDevice(String).L’erreur de constructeur concerne les arguments, et non le champ privé. Remplacer l’appel par
new Device(label)crée un objet distinct et ne résout pas le problème dusuper()implicite. Transmets plutôt le libellé au constructeur de la superclasse. - 8ExempleDélègue le choix d’une unité par défaut facultative
Ajoute ce constructeur dans
Sensor:Sensor(String label) { this(label, "unknown"); }new Sensor("Hall")délègue au constructeur à deux arguments existant, qui appellesuper(label). L’objet obtenu porte le libellé « Hall » et l’unité « unknown ». N’écris pas aussisuper(...)dans le constructeur qui délègue : un constructeur comporte au plus un appel explicite de constructeur. - 9LienValide un paramètre avant la construction
La version standard de Java 25 autorise certaines opérations avant un appel explicite de constructeur. Par exemple, un constructeur de capteur peut rejeter un libellé reçu vide avant d’appeler
super(label).Ce code exécuté en amont peut utiliser les paramètres, mais ne peut pas lire les champs de l’objet ni appeler ses méthodes d’instance. Certaines affectations simples aux champs de la classe courante sans initialiseur dans leur déclaration sont aussi autorisées. Pour les versions standard antérieures du langage, garde l’appel en première position.
- 10DéfiPrévois un message de constructeur
Dans une autre version,
Deviceaffiche « device » après son propre appel à la superclasse.Sensoraffiche « sensor » aprèssuper(label). Quel message apparaît en premier lors de la création d’un capteur, et pourquoi ?---reveal---
« device » apparaît en premier. Le constructeur du capteur appelle celui de l’appareil avant de poursuivre jusqu’à sa propre instruction d’affichage. L’objet est un capteur pendant toute la construction ; ces messages montrent l’ordre du travail des constructeurs, et non des changements de sa classe.
- 11DéfiCorrige l’affectation d’un champ hérité
Une nouvelle sous-classe tente d’initialiser le libellé privé de l’appareil avec
this.label = label;et ometsuper(label). Quels sont les deux problèmes liés que cela crée ?---reveal---
La sous-classe ne peut pas affecter directement une valeur au champ privé. Le
super()implicite n’a pas non plus de constructeur d’appareil correspondant. Utilisesuper(label)pour queDeviceaffecte son propre état privé, puis affecte uniquement les champs déclarés dans la sous-classe. - 12RécapitulatifLocalise un échec de construction
Lorsqu’un sous-type ne peut pas être construit, localise le premier appel de constructeur sans constructeur cible correspondant et accessible. Vérifie ensuite que chaque classe initialise les champs qu’elle possède.
Effectue aussi le test de la relation : un sous-type est utilisable comme son type de base ; un objet contenu doit être placé dans un champ. Ces vérifications traitent des erreurs de conception et de compilation de nature différente.
2Affiche et compare correctement des entrées de catalogue
14 étapes
Enregistre cette unité pour suivre chaque Leçon Interactive pas à pas.
- 1IntroductionChoisis ce que doit représenter une entrée de catalogue
- 2ExempleÉtends une description de base
- 3ConseilÉvite qu’une méthode redéfinie s’appelle elle-même
- 4DéfiVérifie un remplacement apparent
- 5ExempleFournis à l’affichage une méthode exacte à appeler
- 6Point cléLis la classe effective sans la modifier
- 7DéfiDiagnostique un texte affiché qui ressemble au résultat par défaut
- 8ExempleDéclare les données utilisées pour l’égalité
- 9ExempleRejette un objet de type incorrect avant de comparer les champs
- 10ExempleCalcule le code de hachage avec les mêmes champs d’égalité
- 11DéfiRepère la surcharge ignorée par une collection
- 12Point cléPréserve les comparaisons dans les deux sens
- 13DéfiDistingue l’égalité des textes de celle des valeurs
- 14RécapitulatifVérifie une classe de valeur par son interface publique
3Construis un contrat et partage son implémentation
18 étapes
Enregistre cette unité pour suivre chaque Leçon Interactive pas à pas.
- 1IntroductionDistingue les données communes de la capacité commune
- 2ExempleCommence une source abstraite de messages
- 3ExempleComplète l’opération manquante
- 4DéfiDétermine si une sous-classe peut être concrète
- 5ExempleNomme la lecture comme capacité
- 6LienFais respecter l’interface par la classe de base existante
- 7ExempleÉcris un code appelant fondé sur le contrat
- 8DéfiCorrige une implémentation dont l’accès est moins ouvert
- 9ExempleAjoute une opération par défaut utile
- 10ExplicationRespecte le rôle des méthodes auxiliaires statiques et privées
- 11ExempleCrée un véritable conflit entre méthodes par défaut
- 12ExempleChoisis explicitement une méthode par défaut
- 13DéfiDonne la priorité à l’implémentation de la classe
- 14Point cléPropose une base sans imposer son utilisation à toutes les implémentations
- 15DéfiPlace un libellé et un constructeur dans le type approprié
- 16ConseilVérifie les nouvelles méthodes par défaut par rapport aux méthodes héritées
- 17DéfiAjoute un nouveau lecteur sans changer sa superclasse existante
- 18RécapitulatifÉvalue un type commun proposé
4Suis un appel de rapport à travers des types généraux
17 étapes
Enregistre cette unité pour suivre chaque Leçon Interactive pas à pas.
- 1IntroductionSuis ce que la variable promet et ce que l’objet fait
- 2ExempleRéférence un rapport spécialisé avec un type général
- 3ExplicationVérifie ce que la référence expose
- 4DéfiChoisis les types d’une liste mixte de rapports
- 5ExempleRésous d’abord l’appel externe
- 6ExempleRésous l’appel interne sur le même objet receveur
- 7ExempleChoisis une surcharge à partir de l’argument déclaré
- 8DéfiDistingue deux décisions dans un même appel
- 9ConseilRepère séparément les champs et les appels statiques dans un suivi
- 10ExempleAppelle sans erreur l’opération propre aux ventes
- 11DéfiPrévois le résultat d’un transtypage lorsque l’objet change
- 12ExempleRassemble les en-têtes par l’opération commune
- 13LienUtilise la comparaison comme autre opération partagée
- 14DéfiTrie les valeurs en choisissant une implémentation
- 15Point cléRéserve les tests de sous-type aux tâches propres à ce sous-type
- 16DéfiPrends en charge un en-tête d’audit sans nouvelle branche
- 17RécapitulatifSuis un appel inconnu dans l’ordre
5Construis un choix fixe et une donnée protégée
15 étapes
Enregistre cette unité pour suivre chaque Leçon Interactive pas à pas.
- 1IntroductionChoisis entre des options nommées et des données regroupées
- 2ExempleDonne aux choix de service leur propre type
- 3ExempleAssocie des nombres utiles aux choix
- 4ConseilGarde les données de service indépendantes de l’ordre de déclaration
- 5DéfiPrévois le résultat d’une recherche et la valeur d’un champ
- 6LienAssocie le comportement au choix fixe
- 7ExempleDéclare les composants d’une demande de livraison
- 8ExempleNormalise les données reçues avant leur affectation
- 9DéfiCompare les demandes normalisées
- 10ExempleTrouve l’objet mutable dans un composant final
- 11ExempleStocke une copie non modifiable
- 12ConseilRenvoie un nouveau record lorsque les valeurs doivent changer
- 13DéfiChoisis la correction adaptée pour un itinéraire qui change
- 14DéfiModélise de nouvelles options de service et les demandes des clients
- 15RécapitulatifExamine le comportement généré avant d’ajouter du code
Héritage et polymorphisme en Java : QCM
Série 1 : Construis des classes et suis le comportement hérité10 Questions Pratiques
Question 1 sur 10
Library stocke plusieurs objets Shelf, et chaque étagère stocke des livres. Comment Library devrait-elle représenter sa relation avec Shelf ?Afficher la réponseMasquer la réponse
Shelf affirmerait aussi que toute bibliothèque est une étagère et peut être utilisée partout où une étagère est attendue. Réutiliser des méthodes d’étagère ne rend pas cette relation « est un » vraie.Question 2 sur 10
Account déclare un champ privé balance et une méthode publique getBalance(). SavingsAccount extends Account. Comment une méthode de SavingsAccount peut-elle lire l’état hérité du compte ?Afficher la réponseMasquer la réponse
Account. L’état privé appartient toujours à l’objet compte d’épargne, mais le code de la sous-classe ne peut pas accéder directement à ce champ. Ajouter super. ne contourne pas la règle d’accès privé.Question 3 sur 10
Que se passe-t-il lors de la compilation de ce code ?
class Machine {
Machine(int serial) { }
}
class Printer extends Machine {
Printer() { }
}
Afficher la réponseMasquer la réponse
super(). Machine ne déclare qu’un constructeur prenant un entier : il n’existe donc aucun constructeur sans argument. Java n’invente pas de valeur d’argument ; Printer() peut rester sans argument si son corps fournit explicitement un numéro de série à super.Question 4 sur 10
Qu’affiche new Leaf(); ?
class Root {
Root() { System.out.print("R"); }
}
class Branch extends Root {
Branch() { System.out.print("B"); }
}
class Leaf extends Branch {
Leaf() { this(7); System.out.print("L"); }
Leaf(int n) { System.out.print(n); }
}
Afficher la réponseMasquer la réponse
Leaf() délègue à Leaf(int), dont l’appel implicite à la superclasse entre dans Branch, puis dans Root. Leurs corps affichent « R » et « B », puis Leaf(int) affiche 7 et le constructeur qui délègue affiche « L ». La délégation ne construit pas la partie racine une seconde fois.Question 5 sur 10
Meter déclare public int read(). Une sous-classe écrit @Override public int read(int offset). Pourquoi la compilation échoue-t-elle ?Afficher la réponseMasquer la réponse
read au lieu de la redéfinir. @Override détecte cette différence ; le retour de type int est compatible avec la méthode initiale.Question 6 sur 10
Qu’affiche la dernière instruction ?
class Parcel {
public String toString() { return "parcel"; }
}
class ChilledParcel extends Parcel {
@Override public String toString() {
return super.toString() + " cold";
}
}
Parcel item = new ChilledParcel();
System.out.println("Send " + item);
Afficher la réponseMasquer la réponse
toString(), et l’objet colis réfrigéré effectif exécute sa redéfinition. Cette méthode obtient « parcel » de super.toString() et y ajoute « cold » précédé d’une espace. Déclarer la référence comme Parcel ne supprime pas la redéfinition.Question 7 sur 10
Label possède uniquement public boolean equals(Label other), qui compare son texte, et hérite de equals(Object). Deux étiquettes distinctes ont le même texte. Les deux références sont stockées dans des variables déclarées comme Object. Que renvoie a.equals(b) ?Afficher la réponseMasquer la réponse
Object, l’appel sélectionne equals(Object), que Label n’a pas redéfinie. L’implémentation héritée compare l’identité des deux objets distincts et renvoie faux. La méthode de même nom qui prend un Label est une surcharge et n’est pas sélectionnée selon la classe de l’argument à l’exécution.Question 8 sur 10
abstract class Sensor déclare public abstract double reading();. Que doit fournir ou hériter une classe concrète LightSensor extends Sensor ?Afficher la réponseMasquer la réponse
Sensor. Une méthode statique ne redéfinit pas une méthode d’instance, et une méthode privée réduirait l’accès requis.Question 9 sur 10
Pourquoi cette classe ne compile-t-elle pas ?
interface Resettable { void reset(); }
class Counter implements Resettable {
void reset() { }
}
Afficher la réponseMasquer la réponse
reset() de l’interface est implicitement publique. L’implémentation a un accès de paquetage : elle réduit donc l’accès promis et la compilation échoue. Une classe implémente une interface ; remplacer cette relation par extends ne corrigerait pas la déclaration de la méthode.Question 10 sur 10
Que renvoie new TimedJob().cost() ?
class Job {
public int rate() { return 3; }
public int cost() { return 4 * rate(); }
}
class TimedJob extends Job {
@Override public int rate() { return 5; }
@Override public int cost() { return super.cost() + 2; }
}
Afficher la réponseMasquer la réponse
Job.cost() par super.cost(). Son appel ordinaire à rate() s’exécute toujours sur l’objet tâche chronométrée : il renvoie donc 5 ; le calcul hérité donne 20 et la redéfinition externe ajoute 2. Utiliser 3 pour l’appel interne reviendrait à désactiver à tort la liaison dynamique dans toute la méthode de la superclasse.Série 2 : Utilise des contrats et des types de référence généraux10 Questions Pratiques
Enregistre cette unité pour voir chaque réponse et t'entraîner sur chaque série.
Question 1 sur 10
TextDocument et une classe Image sans lien de parenté doivent toutes deux prendre en charge exportText(). Chacune étend déjà une classe différente. Quel type commun exprime le mieux cette capacité ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 2 sur 10
Ce code compile-t-il, et pourquoi ?
interface Named { String name(); }
class Entity {
public String name() { return "entity"; }
}
class Asset extends Entity implements Named { }
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 3 sur 10
Que renvoie new Screen().label() ?
interface Tagged {
default String label() { return "tagged"; }
}
class Panel {
public String label() { return "panel"; }
}
class Screen extends Panel implements Tagged { }
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 4 sur 10
Fast et Safe sont des interfaces sans lien de parenté, qui possèdent chacune default String mode(). Une classe concrète Service implémente les deux et n’hérite d’aucune implémentation de mode() issue d’une classe. Que doit faire Service ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 5 sur 10
Robot implémente Movable. Dans Movable unit = new Robot();, quel est le type déclaré de unit ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 6 sur 10
Vehicle possède move(), et seule Bicycle possède ringBell(). Bicycle extends Vehicle. Que se passe-t-il pour v.ringBell() après Vehicle v = new Bicycle(); ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 7 sur 10
interface Percent déclare static int cap(int n), et Gauge implements Percent ne déclare aucune méthode cap. Comment le code doit-il appeler la méthode auxiliaire de l’interface avec 120 ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 8 sur 10
area(). Elle doit accepter aussi bien une ArrayList<Shape> qu’une LinkedList<Shape>. Quel type de paramètre expose les opérations de liste nécessaires ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 9 sur 10
Que se passe-t-il lors de la compilation et de l’exécution de new Row().render() ?
interface Renderable { String render(); }
abstract class Base implements Renderable {
public String render() { return "[" + body() + "]"; }
protected abstract String body();
}
class Row extends Base {
@Override protected String body() { return "row"; }
}
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 10 sur 10
Report avec un état privé pour le titre, un constructeur qui initialise cet état et une méthode abstraite body(). Les rapports et les objets graphiques sans lien de parenté doivent aussi pouvoir être imprimés. Quelle structure préserve ces deux exigences ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Série 3 : Suis les appels, les membres masqués et les transtypages vérifiés10 Questions Pratiques
Enregistre cette unité pour voir chaque réponse et t'entraîner sur chaque série.
Question 1 sur 10
Shape s = null; est suivi de s instanceof Circle. Circle extends Shape. Quel est le résultat du test ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 2 sur 10
Que se passe-t-il pour la dernière instruction ?
class Animal { }
class Fox extends Animal { }
Animal a = new Animal();
Fox f = (Fox) a;
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 3 sur 10
Que renvoie pick(value) ?
static String pick(Object x) { return "object"; }
static String pick(CharSequence x) { return "sequence"; }
// Inside a method of the class containing these overloads:
Object value = "notes";
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 4 sur 10
Quel texte est produit par p.label + ":" + p.show() ?
class Parent {
String label = "P";
String show() { return label; }
}
class Child extends Parent {
String label = "C";
@Override String show() { return label; }
}
Parent p = new Child();
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 5 sur 10
List<Vehicle> et peut ajouter un bus. Bicycle extends Vehicle et Bus extends Vehicle. Pourquoi ne peut-on pas lui transmettre une List<Bicycle> ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 6 sur 10
Que renvoie b.test() ?
class Base {
String use(Object x) { return "BO"; }
String use(String x) { return "BS"; }
}
class Sub extends Base {
@Override String use(Object x) { return "SO"; }
@Override String use(String x) { return "SS"; }
String test() {
Object text = "x";
return super.use(text) + ":" + use(text);
}
}
Sub b = new Sub();
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 7 sur 10
Qu’affiche new Sub(); avec la version standard de Java 25 ?
class Base {
Base() { System.out.print(value()); }
int value() { return -1; }
}
class Sub extends Base {
int n = 8;
Sub() { super(); n = 11; }
@Override int value() { return n; }
}
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 8 sur 10
Qu’affiche ce code ? Tool ne déclare aucune méthode.
class Tool { }
class Drill extends Tool {
int speed() { return 6; }
}
Tool tool = new Drill();
Object copy = tool;
if (copy instanceof Drill d) {
System.out.print(d.speed());
}
System.out.print(":" + (copy == tool));
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 9 sur 10
Creator possède public Object create(). TextCreator extends Creator la redéfinit. Quelle combinaison de type de retour et d’accès est valide ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 10 sur 10
Document. Sa méthode d’aperçu utilise des branches instanceof pour calculer le nombre de mots de chaque document. De nouvelles classes de documents sont souvent ajoutées. Quel changement permet aux nouvelles classes de prendre en charge l’aperçu sans modifier cet enchaînement de tests de type ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Série 4 : Modélise des valeurs, compare-les et trie-les10 Questions Pratiques
Enregistre cette unité pour voir chaque réponse et t'entraîner sur chaque série.
Question 1 sur 10
enum Status { NEW, SENT, DONE }. Que fait Status.valueOf("sent") ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 2 sur 10
record Reading(int count, String unit) { }, quel appel généré lit le nombre depuis une variable r ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 3 sur 10
enum Band { LOW(2), HIGH(9); ... } stocke l’argument du constructeur dans un champ immuable code. Une nouvelle constante MID(5) est insérée entre LOW et HIGH. Comment changent ordinal() de HIGH et son code stocké ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 4 sur 10
Token inégaux renvoient le même code de hachage. Leurs implémentations de equals(Object) comparent correctement les champs qui définissent chaque jeton. Ce code de hachage partagé viole-t-il le contrat d’égalité et de hachage ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 5 sur 10
record Result(int seconds) implements Comparable<Result> renvoie Integer.compare(seconds, other.seconds). Quel est le signe de new Result(418).compareTo(new Result(405)) ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 6 sur 10
Qu’affiche la dernière instruction ?
record Tag(String text) {
Tag { text = text.trim(); }
}
Tag a = new Tag(" north ");
Tag b = new Tag("north");
System.out.println(a.equals(b));
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 7 sur 10
Ticket.equals de la classe de base accepte tout Ticket ayant le même code. PriorityTicket extends Ticket accepte uniquement les objets PriorityTicket et compare aussi la priorité. Un billet simple t et un billet prioritaire p ont le même code. Que renvoient t.equals(p) et p.equals(t), et cela respecte-t-il la symétrie ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 8 sur 10
Qu’affiche ce code ? List et ArrayList sont importées.
record Batch(List<String> names) { }
var original = new ArrayList<String>(List.of("Rin"));
Batch first = new Batch(original);
Batch second = new Batch(new ArrayList<>(original));
original.add("Sol");
System.out.println(first.equals(second) + ":"
+ first.names().size() + ":" + second.names().size());
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 9 sur 10
Que se passe-t-il lors de l’exécution de Collections.sort(list) ? Les types java.util nécessaires sont importés.
record Rank(int score) implements Comparable<Rank> {
public int compareTo(Rank other) {
return Integer.compare(score, other.score);
}
}
List<Rank> list = List.of(new Rank(8), new Rank(3));
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 10 sur 10
Que renvoie Rule.DOUBLE.apply(Rule.NEXT.apply(4)) ?
enum Rule {
NEXT {
int apply(int n) {
return switch (n) { case 4 -> 6; default -> n + 1; };
}
},
DOUBLE { int apply(int n) { return 2 * n; } };
abstract int apply(int n);
}
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Série 5 : Combine les règles Java et diagnostique les erreurs10 Questions Pratiques
Enregistre cette unité pour voir chaque réponse et t'entraîner sur chaque série.
Question 1 sur 10
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 2 sur 10
Que renvoie new Terminal().name() ?
interface Basic {
default String name() { return "basic"; }
}
interface Detailed extends Basic {
default String name() { return "detail"; }
}
class Terminal implements Basic, Detailed { }
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 3 sur 10
List<Notifier> contient des objets de plusieurs classes sans lien de parenté qui implémentent Notifier.send(). Une boucle appelle n.send() sur chaque élément. Une nouvelle classe PushNotifier implémente correctement le même contrat. Quel changement faut-il apporter à la boucle pour prendre en charge ses instances ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 4 sur 10
Qu’affiche ce code avec la version standard de Java 25 ?
class Base {
Base(int n) { System.out.print("B" + n); }
}
class Sub extends Base {
Sub(int n) {
if (n < 0) throw new IllegalArgumentException();
super(n + 1);
System.out.print("S" + n);
}
}
new Sub(2);
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 5 sur 10
Code définit l’égalité et le hachage à partir de son champ de texte. Un HashSet<Code> contient c avec le texte « red ». Le programme doit remplacer son texte par « blue », conserver le même objet et garantir que la recherche dans l’ensemble reste fiable. Dans quel ordre doit-il mettre à jour l’objet et l’ensemble ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 6 sur 10
Que produit x.call(arg) + ":" + x.call("x") ?
class Alpha {
String call(Object x) { return "AO"; }
String call(String x) { return "AS"; }
}
class Beta extends Alpha {
@Override String call(Object x) { return "BO"; }
@Override String call(String x) { return "BS"; }
}
Alpha x = new Beta();
Object arg = "data";
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 7 sur 10
Qu’affiche la dernière instruction ? List et ArrayList sont importées.
record Group(List<String> names) {
Group { names = List.copyOf(names); }
}
var original = new ArrayList<String>();
original.add("Uma");
Group group = new Group(original);
original.add("Zed");
System.out.println(group.names().size() + ":" + original.size());
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 8 sur 10
Que se passe-t-il lors de la compilation de ce code ?
interface Ready {
default String state() { return "ready"; }
}
abstract class Base {
public abstract String state();
}
class Console extends Base implements Ready { }
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 9 sur 10
record Player(String name, int points) ont des noms différents mais le même nombre de points. Leur méthode personnalisée compareTo ne compare que les points avec Integer.compare. Que donnent equals et compareTo pour ces deux objets ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 10 sur 10
interface Storable { String saveText(); }. De nombreuses nouvelles implémentations partagent un champ privé de chemin, une validation dans le constructeur et du code auxiliaire, tandis que certaines implémentations existantes étendent d’autres classes. Comment la bibliothèque peut-elle proposer cette réutilisation tout en préservant l’interface de programmation commune ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Tu bloques sur ton propre exercice ? Aide aux Devoirs
Mots-Clés
A
Accès protégé
Accesseur
count est count() : getCount() n’est donc pas disponible sauf s’il est déclaré explicitement.Annotation @Override
@Override, demande au compilateur de vérifier qu’une méthode redéfinit ou implémente une méthode héritée.tostring() lorsque le programmeur voulait remplacer toString().Appel d’un constructeur de la superclasse
super(...) pour transmettre des arguments à un constructeur de la superclasse directe pour l’objet courant.Person, qui possède le champ privé du nom.C
Capacité
Chaîne de constructeurs
ClassCastException
Animal est transtypé vers sa sous-classe plus spécifique Fox.Classe abstraite
Shape peut stocker un nom tout en exigeant que les cercles et les rectangles concrets fournissent une aire.Classe concrète
Rectangle est une classe concrète dès qu’elle complète l’opération d’aire exigée par sa classe de base abstraite pour les formes.Classe effective
getClass() indépendamment du type déclaré de sa référence.Student même lorsqu’une méthode le reçoit par un paramètre Person.Code de hachage
Collision de hachage
Comparable
compareTo.Comparable<Runner> pour qu’une méthode de tri puisse classer les coureurs selon leur temps d’arrivée.Composant de record
seconds peut être lu sur un coureur avec la méthode générée runner.seconds().Composition
Customer de contenir une Address sans que chaque client puisse être utilisé comme une adresse.Conflit entre méthodes par défaut
SmartLamp à redéfinir les descriptions correspondantes de Lamp et Speaker.Constante d’énumération
CupSize.LARGE peut stocker une capacité de 500 mL et être comparée par identité.Constante d’interface
Chargeable.FULL peut contenir 100 tandis que chaque appareil stocke son propre champ de batterie changeant.Constructeur canonique
Point(int x, int y) accepte la coordonnée x suivie de la coordonnée y.Constructeur compact
Constructeur par défaut
super().Person(String name) supprime le constructeur par défaut sur lequel une sous-classe pouvait s’appuyer.Contrat d’égalité
Copie non modifiable
List.copyOf protège la liste des membres d’une équipe contre les ajouts à la liste mutable de l’appelant.Corps de classe propre à une constante
MULTIPLY d’implémenter une opération différemment de la constante ADD du type énuméré.Corps de constructeur flexible
D
Dépassement de capacité d’un entier
Integer.compare évite cette erreur de comparaison.É
Égalité de rang
compareTo renvoie zéro, ce qui signifie que deux objets occupent le même rang de comparaison selon la règle d’ordre choisie.Égalité de valeur
État partagé
E
Expression switch
H
Héritage
Student de réutiliser Person.getName() tout en ajoutant ses propres crédits et son comportement d’étude.Héritage simple
Student et Teacher comme superclasses directes.I
Identité
a == b faux.Immuabilité superficielle
Implémentation
Entity peut satisfaire Named.name() pour une sous-classe Asset.Implémentation partielle
Storable peut partager la validation du chemin tandis que les classes concrètes fournissent le texte à enregistrer.Initialiseur de champ
instanceof
instanceof permet au code de vérifier si une personne est de type étudiant avant d’utiliser une méthode propre aux étudiants.Interface
Chargeable permet à un téléphone et à une trottinette de prendre en charge les mêmes appels tout en se chargeant à des vitesses différentes.Interface plus spécifique
Detailed fournit la méthode name() choisie lorsqu’elle étend Basic et remplace sa méthode par défaut.L
Liaison dynamique
hello() qui appelle who() d’atteindre B.who() lorsque l’objet receveur appartient à la classe B.M
Masquage de champ
p.label à lire le champ parent tandis que p.show() exécute une redéfinition de la classe enfant qui utilise un autre libellé.Masquage de méthode
Méthode abstraite
area() empêche une forme concrète inachevée d’utiliser silencieusement une méthode provisoire qui renvoie une aire nulle.Méthode concrète
Shape.describe() peut réutiliser le nom et appeler l’implémentation de area() de chaque sous-classe.Méthode finale
Méthode par défaut
needsCharge() peut utiliser batteryPercent() sans connaître l’organisation des champs privés d’un appareil.Méthode privée d’interface
Méthode statique d’interface
Chargeable.clamp(130) peut renvoyer 100 sans créer d’objet appareil.O
Object
Object peut contenir un étudiant ou un tableau, et tous deux prennent en charge les méthodes que possède chaque objet.Objet receveur
shape.area(), l’objet receveur est l’objet forme, même lorsque la variable a un type déclaré abstrait.Ordre naturel
P
Polymorphisme
area() sur des cercles, des rectangles et des triangles sans branches distinctes selon le type.R
Rang ordinal
LOW, HIGH, le rang ordinal de HIGH passe de 1 à 2 lorsque MID est inséré avant elle.Record
Point(int x, int y) regroupe deux coordonnées et fournit une égalité fondée sur les composants sans méthodes écrites à la main.Redéfinition
yearlyPay() permet à un responsable d’ajouter une prime tandis que les appelants continuent d’utiliser l’opération du salarié.Représentation textuelle
toString(), utilisée lorsque l’objet est affiché ou joint à un autre texte.S
Signature de méthode
Object à String modifie la signature de méthode et crée une surcharge.Sous-classe
GraduateStudent peut s’appuyer sur Student tout en restant utilisable partout où une Person est attendue.Superclasse
Person est la superclasse de Student : une méthode qui accepte une personne peut donc aussi recevoir un étudiant.Superinterface
Chargeable est une superinterface de SmartDevice lorsque cette dernière l’étend en même temps que Connectable.Surcharge
print(String text) à côté de print(int number) sans remplacer aucune des deux méthodes.Symétrie
book.equals(ebook) est vrai mais que ebook.equals(book) est faux pour ces mêmes objets.T
Transitivité
Transtypage ascendant
Chargeable conserve ses données de batterie mais n’expose que les opérations disponibles par ce type.Transtypage descendant
Type de retour covariant
String lorsque la méthode de la superclasse renvoie Object.Type déclaré
Person p = new Student(...), le type déclaré est Person : les méthodes propres aux étudiants ne sont donc pas directement disponibles.Type effectif
Person peut désigner un étudiant dont le type effectif reste Student après le transtypage ascendant.Type énuméré
NEW, SENT et DONE.Type interface
List comme type interface permet à une méthode d’accepter aussi bien des listes fondées sur des tableaux que des listes chaînées.V
Variable de motif
student dans person instanceof Student student expose sans erreur la méthode d’identifiant de l’objet correspondant.- 1Premiers programmes Java
- 2Conditions et boucles en Java
- 3Méthodes Java
- 4Listes et tableaux en Java
- 5Classes et objets en Java
- 6Références et égalité en Java
- 7HashMap et collections en Java
- 8Héritage et polymorphisme en Java
- 9Exceptions et fichiers en Java
- 10Paquetages et tests unitaires Java
- 11Généricité et flux en Java
À propos de cette unité
Crée ton propre Matériel d'Étude
Transforme tes notes, diapos et documents en Guides d'Étude, Questions Pratiques, Flashcards et 6 autres outils.