Au chapitre 2, chaque classe partait de zéro. Un étudiant et un salarié ont pourtant beaucoup en commun : un nom, un prénom, un âge. Recopier ces attributs dans chaque classe, encore et encore, devient vite pénible, n’est-ce pas ?
Dans ce chapitre, vous apprenez à faire reprendre à une classe ce qu’une autre a déjà prévu, puis à appeler une méthode selon l’objet concret, puis à décrire un rôle que plusieurs classes peuvent tenir.
3.1Héritage
Un étudiant est une personne, avec quelque chose en plus : une filière.
En Java, vous n’écrivez pas une deuxième fois le nom, le prénom et l’âge.
Vous dites : la classe Etudiant reprend la classe Personne, puis vous ajoutez seulement ce qui est nouveau.
C’est l’héritage.
3.1.1 – extends
Le mot-clé extends relie la nouvelle classe à celle qui existe déjà .
Personne est la classe mère.
Etudiant est la classe fille.
La fille reçoit les attributs et les méthodes de la mère.
Vous pouvez ensuite lui donner ses propres attributs et ses propres méthodes.
Après extends, vous écrivez le nom de la classe mère.
public class Personne {
private String nom;
private String prenom;
private int age;
public void setNom(String nom) {
this.nom = nom;
}
public void setPrenom(String prenom) {
this.prenom = prenom;
}
public void setAge(int age) {
this.age = age;
}
public String getNom() {
return nom;
}
public String getPrenom() {
return prenom;
}
public int getAge() {
return age;
}
public void afficher() {
System.out.println(prenom + " " + nom + ", " + age + " ans");
}
}
public class Etudiant extends Personne {
private String filiere;
public void setFiliere(String filiere) {
this.filiere = filiere;
}
public String getFiliere() {
return filiere;
}
}
public class MainClass {
public static void main(String[] args) {
Etudiant youssef = new Etudiant();
youssef.setNom("Alaoui");
youssef.setPrenom("Youssef");
youssef.setAge(22);
youssef.setFiliere("Informatique");
youssef.afficher();
System.out.println(youssef.getFiliere());
}
}
setNom, setPrenom, setAge et afficher sont écrits dans Personne.
Vous les appelez quand même sur youssef, parce qu’un étudiant est une personne.
setFiliere et getFiliere n’existent que dans Etudiant.
3.1.2 – Une seule classe mère
Après extends, Java n’accepte qu’un seul nom.
Etudiant reprend Personne.
Il ne peut pas reprendre en même temps une deuxième classe, par exemple Contact.
public class Personne {
private String nom;
public void setNom(String nom) {
this.nom = nom;
}
}
public class Etudiant extends Personne {
private String filiere;
public void setFiliere(String filiere) {
this.filiere = filiere;
}
public String getFiliere() {
return filiere;
}
}
public class MainClass {
public static void main(String[] args) {
Etudiant youssef = new Etudiant();
youssef.setNom("Alaoui");
youssef.setFiliere("Informatique");
System.out.println(youssef.getFiliere());
}
}
public class Etudiant extends Personne, Contact.
Une classe fille a une seule classe mère.
Ici, Etudiant reprend seulement Personne.
3.1.3 – Toute classe reprend Object
Au chapitre 2, vous avez utilisé toString, equals et hashCode.
Ces trois méthodes sont prévues dans une classe que Java fournit : Object.
Toute classe la reprend, même si vous n’écrivez pas extends Object.
Quand vous écrivez class Etudiant extends Personne, la chaîne est donc Etudiant, puis Personne, puis Object.
public class Personne {
private String nom;
public void setNom(String nom) {
this.nom = nom;
}
public String getNom() {
return nom;
}
}
public class MainClass {
public static void main(String[] args) {
Personne p = new Personne();
p.setNom("Alaoui");
System.out.println(p.getNom());
System.out.println(p.toString());
}
}
getNom affiche Alaoui : vous avez écrit cette méthode dans Personne.
toString n’est pas écrite dans Personne.
L’affichage contient le nom de la classe, puis un code. Cette méthode vient de Object.
3.1.4 – super
Le mot super désigne la classe mère, depuis la classe fille.
Vous l’utilisez dans deux cas : appeler le constructeur de la mère, ou appeler une de ses méthodes.
Le constructeur de la mère
Au chapitre 2, un constructeur prépare l’objet au moment du new.
Avec l’héritage, la partie « personne » doit être préparée avant la filière.
Vous écrivez super(...) pour appeler le constructeur de Personne.
super(...) est la première instruction du constructeur de la fille.
public class Personne {
private String nom;
private String prenom;
private int age;
public Personne(String nom, String prenom, int age) {
this.nom = nom;
this.prenom = prenom;
this.age = age;
}
public void afficher() {
System.out.println(prenom + " " + nom + ", " + age + " ans");
}
}
public class Etudiant extends Personne {
private String filiere;
public Etudiant(String nom, String prenom, int age, String filiere) {
super(nom, prenom, age);
this.filiere = filiere;
}
public String getFiliere() {
return filiere;
}
public void presenter() {
super.afficher();
System.out.println("Filière : " + filiere);
}
}
public class MainClass {
public static void main(String[] args) {
Etudiant youssef = new Etudiant("Alaoui", "Youssef", 22, "Informatique");
youssef.presenter();
}
}
super(nom, prenom, age) range le nom, le prénom et l’âge grâce au constructeur de Personne.
Ensuite seulement, this.filiere = filiere range la filière.
super.afficher() appelle la méthode afficher écrite dans la mère.
presenter() ajoute ensuite la filière.
Si vous n’écrivez aucun constructeur dans la fille, Java appelle super() sans argument.
Cela fonctionne quand la mère a un constructeur sans paramètre.
Dès que la mère n’a qu’un constructeur avec paramètres, comme ici, la fille écrit elle-même super(nom, prenom, age).
super(...) est refusée à la compilation.
Le constructeur de la mère passe en premier.
Appel implicite ou explicite Ă super()
Quand la mère a un constructeur sans paramètre, vous pouvez omettre super().
Java l’ajoute quand même, en première instruction du constructeur de la fille.
L’appel est alors implicite.
Si vous écrivez super() vous-même, l’appel est explicite.
Le résultat est le même.
Exercice de réflexion. Aucun constructeur ci-dessous n’écrit super(). Qu’affiche new Carre(), et dans quel ordre ?
public class Forme {
public Forme() {
System.out.println("Je suis une Forme");
}
}
public class Rectangle extends Forme {
public Rectangle() {
System.out.println("Je suis un Rectangle");
}
}
public class Carre extends Rectangle {
public Carre() {
System.out.println("Je suis un Carre");
}
}
public class MainClass {
public static void main(String[] args) {
Carre objCarre = new Carre();
}
}
3.1.5 – @Override
Redéfinir une méthode, c’est écrire dans la fille une méthode qui porte le même nom et les mêmes paramètres que celle de la mère. Au moment de l’appel, Java utilise la version de la fille.
Vous placez @Override juste au-dessus de cette méthode.
Ce mot ne change pas le comportement.
Il demande au compilateur de vérifier que vous redéfinissez vraiment une méthode de la mère.
Si le nom ou les paramètres ne correspondent pas, la compilation s’arrête. Pratique, non ?
public class Etudiant extends Personne {
private String filiere;
public Etudiant(String nom, String prenom, int age, String filiere) {
super(nom, prenom, age);
this.filiere = filiere;
}
@Override
public void afficher() {
super.afficher();
System.out.println("Filière : " + filiere);
}
}
public class MainClass {
public static void main(String[] args) {
Etudiant youssef = new Etudiant("Alaoui", "Youssef", 22, "Informatique");
youssef.afficher();
}
}
Personne, grâce à super.afficher().
La deuxième ligne vient de Etudiant.
L’appel s’écrit toujours afficher(), et c’est la version de la fille qui s’exécute.
affiche() au lieu de afficher(),
sans @Override, Java ajoute une nouvelle méthode.
L’ancienne afficher() reste en place, et votre texte de filière ne s’affiche pas.
Avec @Override, cette faute est refusée à la compilation.
3.1.6 – Surcharge d’une méthode
Au chapitre 2, vous avez surchargé des constructeurs : même nom, paramètres différents, et Java choisit selon les arguments du new.
Vous pouvez faire la même chose avec une méthode.
C’est la surcharge.
public class Personne {
private String nom;
private String prenom;
public Personne(String nom, String prenom) {
this.nom = nom;
this.prenom = prenom;
}
public void afficher() {
System.out.println(prenom + " " + nom);
}
public void afficher(String titre) {
System.out.println(titre + " " + prenom + " " + nom);
}
}
public class MainClass {
public static void main(String[] args) {
Personne youssef = new Personne("Alaoui", "Youssef");
youssef.afficher();
youssef.afficher("M.");
}
}
afficher.
La première n’a pas de paramètre. La seconde reçoit un String.
Le compilateur choisit selon l’appel : afficher() ou afficher("M.").
@Override au-dessus de afficher(String titre), le compilateur refuse : cette méthode ne redéfinit pas afficher().
3.1.7 – protected
Au chapitre 2, vous avez vu trois niveaux : private, default (aucun mot-clé) et public.
Il en reste un, prévu pour l’héritage : protected.
Un membre protected est utilisable Ă trois endroits :
- dans la classe elle-mĂŞme ;
- dans les classes qui l’étendent, même si elles sont dans un autre package ;
- dans les autres classes du mĂŞme package.
Une classe qui n’étend pas Personne et qui se trouve dans un autre package ne voit pas ce membre.
public class Personne {
protected String nom;
private int age;
public Personne(String nom, int age) {
this.nom = nom;
this.age = age;
}
}
public class Etudiant extends Personne {
public Etudiant(String nom, int age) {
super(nom, age);
}
public void presenter() {
System.out.println(nom);
// System.out.println(age); // le compilateur refuse : age est private
}
}
public class MainClass {
public static void main(String[] args) {
Etudiant amine = new Etudiant("Tazi", 21);
amine.presenter();
}
}
private quand la fille passe par un getter public.
Passez à protected quand la fille doit lire ou modifier l’attribut dans son propre code.
3.1.8 – Empêcher l’héritage : classe final
Au chapitre 2, final accompagnait static pour fixer une constante.
Placé devant class, il a un autre rôle : personne ne peut étendre cette classe.
public final class Personne {
private String nom;
public void setNom(String nom) {
this.nom = nom;
}
public String getNom() {
return nom;
}
}
// ❌ public class Etudiant extends Personne { }
// Le compilateur refuse : Personne est final.
public class MainClass {
public static void main(String[] args) {
Personne p = new Personne();
p.setNom("Alaoui");
System.out.println(p.getNom());
}
}
public class Etudiant extends Personne est refusé à la compilation, parce que Personne est final.
3.1.9 – Empêcher la redéfinition : méthode final
Une classe peut rester extensible, tout en bloquant une méthode.
Vous placez final devant cette méthode.
Une fille peut encore ajouter ses propres méthodes. Elle ne peut pas redéfinir celle qui est final.
public class Personne {
private String nom;
private String prenom;
public Personne(String nom, String prenom) {
this.nom = nom;
this.prenom = prenom;
}
public final void afficher() {
System.out.println(prenom + " " + nom);
}
public String getPrenom() {
return prenom;
}
public String getNom() {
return nom;
}
}
public class Etudiant extends Personne {
private String filiere;
public Etudiant(String nom, String prenom, String filiere) {
super(nom, prenom);
this.filiere = filiere;
}
public String getFiliere() {
return filiere;
}
// ❌ @Override
// public void afficher() { }
// Le compilateur refuse : afficher est final dans Personne.
}
public class MainClass {
public static void main(String[] args) {
Etudiant sara = new Etudiant("Idrissi", "Sara", "Gestion");
sara.afficher();
System.out.println(sara.getFiliere());
}
}
Etudiant une méthode afficher() avec @Override,
le compilateur s’arrête. afficher est final dans Personne.
getFiliere reste autorisée : c’est une méthode nouvelle, pas une redéfinition.
3.2Polymorphisme
Vous venez de voir qu’un étudiant est une personne.
Java vous autorise donc à ranger un étudiant dans une variable de type Personne.
L’appel d’une méthode redéfinie suit alors l’objet réel, pas seulement le type écrit à gauche du =.
C’est le polymorphisme : une même écriture, un comportement qui dépend de l’objet concret.
3.2.1 – Type déclaré et type réel
Regardez cette ligne :
Personne p = new Etudiant("Alaoui", "Youssef", 22, "Informatique");
- Le type déclaré est le type de la variable :
Personne. C’est ce que le compilateur consulte. - Le type réel est le type de l’objet créé par
new:Etudiant. C’est ce qui vit en mémoire à l’exécution.
Le compilateur n’accepte que les méthodes du type déclaré.
Vous pouvez appeler p.afficher(), parce que Personne possède afficher.
p.getFiliere() est refusé : getFiliere n’existe pas dans Personne, même si l’objet réel est un étudiant.
À l’exécution, si la méthode a été redéfinie, Java appelle la version du type réel.
Ici, p.afficher() lance afficher de Etudiant.
public class Personne {
private String nom;
private String prenom;
private int age;
public Personne(String nom, String prenom, int age) {
this.nom = nom;
this.prenom = prenom;
this.age = age;
}
public void afficher() {
System.out.println(prenom + " " + nom + ", " + age + " ans");
}
}
public class Etudiant extends Personne {
private String filiere;
public Etudiant(String nom, String prenom, int age, String filiere) {
super(nom, prenom, age);
this.filiere = filiere;
}
public String getFiliere() {
return filiere;
}
@Override
public void afficher() {
super.afficher();
System.out.println("Filière : " + filiere);
}
}
public class MainClass {
public static void main(String[] args) {
Personne p = new Etudiant("Alaoui", "Youssef", 22, "Informatique");
p.afficher();
// ❌ p.getFiliere();
// Le compilateur refuse : getFiliere n’existe pas dans Personne.
// ❌ Etudiant e = new Personne("Alaoui", "Youssef", 22);
// Le compilateur refuse : une Personne n’est pas un Etudiant.
}
}
p a le type déclaré Personne.
L’objet créé a le type réel Etudiant.
p.afficher() lance donc la version de Etudiant : le nom, puis la filière.
p.getFiliere() ne compile pas.
getFiliere n’existe pas dans Personne, même si l’objet réel est un étudiant.
Et une méthode static ?
Une méthode static ne se redéfinit pas.
@Override au-dessus d’elle est refusé : Java la masque seulement.
L’appel suit alors le type déclaré, ou le nom de classe écrit devant le point.
public class Personne {
public static void bonjour() {
System.out.println("Bonjour de Personne");
}
}
public class Etudiant extends Personne {
public static void bonjour() {
System.out.println("Bonjour de Etudiant");
}
// ❌ @Override
// public static void bonjour() { }
// Le compilateur refuse : une méthode static ne se redéfinit pas.
}
public class MainClass {
public static void main(String[] args) {
Personne.bonjour();
Etudiant.bonjour();
Personne p = new Etudiant();
p.bonjour();
// ❌ Cet appel n’affiche pas « Bonjour de Etudiant ».
// Le type déclaré Personne choisit bonjour.
}
}
Personne.bonjour() affiche Bonjour de Personne.
Etudiant.bonjour() affiche Bonjour de Etudiant.
p.bonjour() affiche encore Bonjour de Personne, parce que p est déclarée Personne.
Pour afficher(), c’était l’objet réel qui choisissait. Pour bonjour(), c’est le type déclaré.
3.2.2 – Dans un tableau
Un tableau a un seul type déclaré pour toutes ses cases.
Si ce type est Personne, chaque case peut recevoir un étudiant ou un salarié.
La boucle appelle toujours afficher(). Chaque objet répond avec sa propre version.
public class Salarie extends Personne {
private String poste;
public Salarie(String nom, String prenom, int age, String poste) {
super(nom, prenom, age);
this.poste = poste;
}
@Override
public void afficher() {
super.afficher();
System.out.println("Poste : " + poste);
}
}
public class MainClass {
public static void main(String[] args) {
Personne[] groupe = new Personne[2];
groupe[0] = new Etudiant("Alaoui", "Youssef", 22, "Informatique");
groupe[1] = new Salarie("Benjelloun", "Fatima", 30, "Comptable");
for (int i = 0; i < groupe.length; i++) {
groupe[i].afficher();
}
}
}
Personne et Etudiant sont ceux du point 3.2.1.
La première case ajoute la filière. La seconde ajoute le poste.
Le tableau a un seul type déclaré, et deux types réels.
groupe[0].getFiliere() ne compile pas.
Pour le compilateur, chaque case est une Personne.
3.2.3 – En paramètre
Une méthode peut recevoir une Personne.
À l’appel, vous lui passez un Etudiant ou un Salarie.
Dans la méthode, l’écriture reste p.afficher(), et c’est le type réel qui choisit la version.
public class MainClass {
public static void presenter(Personne p) {
p.afficher();
}
public static void main(String[] args) {
presenter(new Etudiant("Alaoui", "Youssef", 22, "Informatique"));
presenter(new Salarie("Benjelloun", "Fatima", 30, "Comptable"));
}
}
presenter ne connaît que Personne.
Le premier appel affiche la filière. Le second affiche le poste.
Les classes Etudiant et Salarie sont celles des points 3.2.1 et 3.2.2.
3.2.4 – instanceof et conversion de type
Parfois, vous avez une variable Personne et vous devez appeler une méthode qui n’existe que sur Etudiant.
Vous vérifiez d’abord le type réel avec instanceof.
Si le test réussit, vous convertissez la référence : c’est un cast, une conversion de type.
public class MainClass {
public static void main(String[] args) {
Personne p = new Etudiant("Tazi", "Amine", 21, "Gestion");
if (p instanceof Etudiant) {
Etudiant e = (Etudiant) p;
System.out.println(e.getFiliere());
}
}
}
p instanceof Etudiant vaut true quand l’objet réel est un Etudiant,
ou une classe qui étend Etudiant.
Si p vaut null, le test vaut false.
L’appel qui suit n’a donc pas lieu, et vous évitez une NullPointerException.
Le cast (Etudiant) p ne crée pas un nouvel objet.
Il indique au compilateur de traiter cette référence comme un étudiant.
Vous pouvez ensuite appeler getFiliere().
Salarie et que vous écrivez quand même (Etudiant) p,
le programme compile, puis s’arrête à l’exécution avec une ClassCastException.
Écrivez instanceof, puis le cast.
3.3Abstraction
Jusqu’ici, Personne restait une classe normale : vous pouviez écrire new Personne(...).
Parfois, le modèle commun est trop général pour exister tout seul.
Vous savez quelles données il possède, et vous savez qu’une action change selon la fille.
La première solution est une classe abstraite.
3.3.1 – Classe abstraite
Une classe abstraite est un modèle incomplet.
Vous écrivez abstract devant class.
Vous ne faites pas new sur cette classe.
Vous créez des objets des classes filles, qui complètent le modèle.
Elle peut déjà contenir des attributs, un constructeur et des méthodes prêtes à l’emploi. Elle peut aussi contenir une méthode abstraite : la signature, un point-virgule, et pas de corps. La fille écrit ce corps. Si elle ne l’écrit pas, elle est elle aussi abstraite.
public abstract class Compte {
private String titulaire;
private double solde;
public Compte(String titulaire, double solde) {
this.titulaire = titulaire;
this.solde = solde;
}
public double getSolde() {
return solde;
}
public void deposer(double montant) {
if (montant > 0) {
solde = solde + montant;
}
}
public abstract double calculerInterets();
}
public class CompteEpargne extends Compte {
public CompteEpargne(String titulaire, double solde) {
super(titulaire, solde);
}
@Override
public double calculerInterets() {
return getSolde() * 0.02;
}
}
public class MainClass {
public static void main(String[] args) {
// Compte c = new Compte("Alaoui", 1000); // le compilateur refuse : classe abstraite
Compte compte = new CompteEpargne("Youssef Alaoui", 1000);
compte.deposer(500);
System.out.println(compte.calculerInterets());
}
}
deposer est déjà écrite : toutes les filles l’ont.
calculerInterets n’a pas de corps dans Compte, parce que le calcul dépend du type de compte.
La variable a le type déclaré Compte. L’objet réel est un CompteEpargne.
À l’exécution, calculerInterets() suit donc l’objet réel, comme au point 3.2.
super(...).
Ce constructeur prépare l’objet. Il ne permet pas d’écrire new Compte(...).
3.3.2 – Interface
Une interface décrit un rôle : une liste de méthodes que la classe promet de fournir.
Elle ne stocke pas le nom, l’âge ou le solde d’un objet.
Vous l’écrivez avec le mot-clé interface.
Une interface n’est pas une classe.
Toutes ses méthodes sont abstraites : aucune n’a de corps.
C’est pour cela qu’on la dit entièrement abstraite.
La classe qui tient ce rôle écrit le corps, avec implements.
Dans la classe, vous déclarez cette méthode public.
public interface Affichable {
void afficher();
}
public class Personne {
private String nom;
private String prenom;
public Personne(String nom, String prenom) {
this.nom = nom;
this.prenom = prenom;
}
public String getNom() {
return nom;
}
public String getPrenom() {
return prenom;
}
}
public class Etudiant extends Personne implements Affichable {
private String filiere;
public Etudiant(String nom, String prenom, String filiere) {
super(nom, prenom);
this.filiere = filiere;
}
@Override
public void afficher() {
System.out.println(getPrenom() + " " + getNom() + " — " + filiere);
}
}
public class MainClass {
public static void main(String[] args) {
Affichable a = new Etudiant("Idrissi", "Sara", "Gestion");
a.afficher();
}
}
Affichable promet seulement afficher().
Vous ne faites pas new Affichable() : une interface n’a pas d’objet.
La variable a a le type déclaré Affichable, et l’objet réel est un Etudiant.
L’appel a.afficher() suit donc cet objet, comme au point 3.2.
@Override vérifie ici que afficher existe bien dans Affichable.
Un attribut dans une interface
Un attribut d’interface n’appartient pas à un objet.
Il ne peut pas contenir le nom d’une personne, qui change d’un étudiant à l’autre.
C’est une constante : vous écrivez le type, le nom et la valeur.
Java complète tout seul avec public, static et final.
public interface Affichable {
int LARGEUR_MAX = 40;
void afficher();
// ❌ private String nom;
// Le compilateur refuse : cet attribut ne peut pas ĂŞtre private.
// Il est public, static et final.
}
public class Etiquette implements Affichable {
@Override
public void afficher() {
System.out.println(LARGEUR_MAX);
System.out.println(Affichable.LARGEUR_MAX);
// Les deux lignes affichent 40.
// Affichable.LARGEUR_MAX devient obligatoire dans deux cas :
// Etiquette implémente une autre interface qui a aussi LARGEUR_MAX,
// ou Etiquette déclare elle-même un attribut LARGEUR_MAX.
}
}
public class MainClass {
public static void main(String[] args) {
System.out.println(Affichable.LARGEUR_MAX);
new Etiquette().afficher();
// ❌ Affichable.LARGEUR_MAX = 80;
// Le compilateur refuse : la valeur est déjà fixée.
}
}
LARGEUR_MAX suffit, parce qu’une seule constante porte ce nom.
Affichable.LARGEUR_MAX la désigne sans aucun doute.
Ce second écrit devient obligatoire dans deux cas.
D’abord, si Etiquette implémente une autre interface qui possède aussi LARGEUR_MAX : le nom seul est ambigu, le compilateur le refuse.
Ensuite, si Etiquette déclare elle-même un attribut LARGEUR_MAX : le nom seul désigne alors l’attribut de la classe.
Pour retrouver la constante de l’interface, vous écrivez Affichable.LARGEUR_MAX.
3.3.3 – Implémentation de plusieurs interfaces
Une classe étend une seule classe. Elle peut en plus implémenter plusieurs interfaces : vous séparez leurs noms par une virgule. Chaque interface ajoute un rôle.
public interface Affichable {
void afficher();
}
public interface Joignable {
String getTelephone();
}
public class Salarie extends Personne implements Affichable, Joignable {
private String poste;
private String telephone;
public Salarie(String nom, String prenom, String poste, String telephone) {
super(nom, prenom);
this.poste = poste;
this.telephone = telephone;
}
@Override
public void afficher() {
System.out.println(getPrenom() + " " + getNom() + " — " + poste);
}
@Override
public String getTelephone() {
return telephone;
}
}
public class MainClass {
public static void main(String[] args) {
Salarie fatima = new Salarie("Benjelloun", "Fatima", "Comptable", "0612345678");
Affichable affichage = fatima;
Joignable contact = fatima;
affichage.afficher();
System.out.println(contact.getTelephone());
}
}
Fatima est un seul objet, avec trois rĂ´les.
Comme Salarie, elle a un poste.
Comme Affichable, on peut l’afficher.
Comme Joignable, on peut lire son téléphone.
Chaque variable ne voit que les méthodes de son type déclaré.
extends indique la classe mère.
implements indique les interfaces.
L’ordre est fixe : extends d’abord, implements ensuite.
Personne est ici la classe de la section précédente : elle fournit getNom et getPrenom.
Salarie les reprend, puis ajoute le poste et le téléphone.
Le nom seul d’une constante n’est plus accepté dès que deux interfaces la portent toutes les deux. C’est l’ambiguïté : le compilateur ne sait pas laquelle choisir. Vous écrivez alors le nom de l’interface devant le point.
public interface Affichable {
int LARGEUR_MAX = 40;
void afficher();
}
public interface Joignable {
int LARGEUR_MAX = 80;
String getTelephone();
}
public class Fiche implements Affichable, Joignable {
@Override
public void afficher() {
// ❌ System.out.println(LARGEUR_MAX);
// Le compilateur refuse : deux constantes portent le mĂŞme nom.
System.out.println(Affichable.LARGEUR_MAX);
System.out.println(Joignable.LARGEUR_MAX);
}
@Override
public String getTelephone() {
return "0612345678";
}
}
public class MainClass {
public static void main(String[] args) {
new Fiche().afficher();
}
}
3.3.4 – Classe abstraite ou interface
Les deux outils servent quand un modèle ne doit pas être créé tel quel avec new.
Ils ne répondent pas au même besoin. Voici comment choisir.
| Classe abstraite | Interface | |
|---|---|---|
| Code déjà écrit | Partiellement : des attributs, et des méthodes déjà écrites | Pas de méthode écrite. Un attribut y est une constante : public, static et final |
| Lien avec la classe | Une classe l’étend avec extends |
Une classe l’implémente avec implements |
new et constructeur |
Vous ne faites pas new. Un constructeur reste possible |
Vous ne faites pas new. Il n’y a pas de constructeur |
| Nombre | Une classe n’étend qu’une seule classe, abstraite ou non | Une classe peut implémenter plusieurs interfaces |
Vous pouvez combiner les deux.
CompteEpargne étend la classe abstraite Compte,
et elle peut aussi implémenter Affichable.
L’héritage porte les données communes. L’interface ajoute le rôle « pouvoir s’afficher ».
En résumé
extendsreprend les membres d’une seule classe mère. Toute classe reprend aussiObject.super(...)appelle le constructeur de la mère, en première instruction.super.methode()appelle une méthode de la mère.@Overrideredéfinit une méthode : même nom, mêmes paramètres. La surcharge change les paramètres.protectedouvre l’accès à la classe, à ses filles, et au même package.finalsur une classe empêcheextends.finalsur une méthode empêche la redéfinition.- Le type déclaré limite les appels. Le type réel choisit la méthode redéfinie, dans une variable, un tableau ou un paramètre.
instanceofprécède le cast(Etudiant) p.- Une classe abstraite partage du code incomplet. Une interface décrit un rôle. Une classe peut en implémenter plusieurs.