Classes et méthodes abstraites en Java
Prérequis
Maîtriser les classes, les objets, l'héritage et les modificateurs d'accès. Comprendre le mot-clé extends et le constructeur super(). Connaître la notion de redéfinition de méthode (@Override).
Objectifs
Distinguer classe concrète et classe abstraite. Déclarer des classes et méthodes abstraites avec le mot-clé abstract. Comprendre les règles d'implémentation dans les sous-classes. Utiliser les classes abstraites pour modéliser des hiérarchies cohérentes.
1. Classe concrète vs classe abstraite
new. Toutes ses méthodes ont une implémentation complète.abstract et peut contenir des méthodes abstraites (sans corps) et des méthodes concrètes (avec corps).Exemple — Instanciation interdite
abstract class Forme {
abstract double surface();
}
Forme f = new Forme(); // Erreur : Forme is abstract; cannot be instantiated
2. Déclarer une classe et des méthodes abstraites
public abstract class NomClasse {
// Méthode abstraite — sans corps, se termine par ;
public abstract typeRetour nomMethode(paramètres);
// Méthode concrète — avec corps, héritée telle quelle
public typeRetour autreMethode() {
// implémentation
}
}
- Une méthode abstraite n'a pas de corps — sa déclaration se termine par
;. - Si une classe contient au moins une méthode abstraite, elle doit être déclarée
abstract. - Une classe abstraite peut contenir des méthodes concrètes, des attributs, des constructeurs et des méthodes
static. - Une sous-classe concrète doit implémenter toutes les méthodes abstraites héritées, sinon elle doit elle-même être déclarée
abstract.
| Élément | Classe abstraite | Classe concrète |
|---|---|---|
Instanciable (new) |
Non | Oui |
| Méthodes abstraites | Autorisées | Interdites |
| Méthodes concrètes | Autorisées | Autorisées |
| Constructeur | Oui (appelé par super()) |
Oui |
| Peut être étendue | Oui | Oui (si non final) |
3. Exemple complet — Hiérarchie de formes géométriques
Le type de base est Forme — chaque forme a une couleur et un comportement commun (getCouleur(), afficher()). La méthode surface() est abstraite car chaque forme la calcule différemment. Les classes Cercle et Rectangle étendent Forme et fournissent leur propre implémentation.
Exemple 1 — Hiérarchie complète (Java 21)
abstract class Forme {
private String couleur;
public Forme(String couleur) {
System.out.println("Constructeur Forme");
this.couleur = couleur;
}
public abstract double surface();
public abstract double perimetre();
public String getCouleur() {
return couleur;
}
public void afficher() {
System.out.printf("Forme : %-12s | Couleur : %-6s | Surface : %8.4f | Périmètre : %8.4f%n",
getClass().getSimpleName(), couleur, surface(), perimetre());
}
}
class Cercle extends Forme {
private double rayon;
public Cercle(String couleur, double rayon) {
super(couleur);
System.out.println("Constructeur Cercle");
this.rayon = rayon;
}
@Override
public double surface() {
return Math.PI * rayon * rayon;
}
@Override
public double perimetre() {
return 2 * Math.PI * rayon;
}
}
class Rectangle extends Forme {
private double hauteur;
private double largeur;
public Rectangle(String couleur, double hauteur, double largeur) {
super(couleur);
System.out.println("Constructeur Rectangle");
this.hauteur = hauteur;
this.largeur = largeur;
}
@Override
public double surface() {
return hauteur * largeur;
}
@Override
public double perimetre() {
return 2 * (hauteur + largeur);
}
}
class Triangle extends Forme {
private double a, b, c;
public Triangle(String couleur, double a, double b, double c) {
super(couleur);
System.out.println("Constructeur Triangle");
this.a = a;
this.b = b;
this.c = c;
}
@Override
public double surface() {
double s = perimetre() / 2;
return Math.sqrt(s * (s - a) * (s - b) * (s - c));
}
@Override
public double perimetre() {
return a + b + c;
}
}
public class Test {
public static void main(String[] args) {
Forme f1 = new Cercle("Rouge", 2.2);
Forme f2 = new Rectangle("Vert", 2, 4);
Forme f3 = new Triangle("Bleu", 3, 4, 5);
System.out.println("\n=== Résultats ===");
f1.afficher();
f2.afficher();
f3.afficher();
}
}
Constructeur Forme Constructeur Cercle Constructeur Forme Constructeur Rectangle Constructeur Forme Constructeur Triangle === Résultats === Forme : Cercle | Couleur : Rouge | Surface : 15.2053 | Périmètre : 13.8230 Forme : Rectangle | Couleur : Vert | Surface : 8.0000 | Périmètre : 12.0000 Forme : Triangle | Couleur : Bleu | Surface : 6.0000 | Périmètre : 12.0000
getClass().getSimpleName() Cette méthode héritée de Object retourne le nom simple de la classe réelle de l'objet — ici "Cercle", "Rectangle" ou "Triangle" selon l'instance. C'est le polymorphisme en action.4. Polymorphisme avec les classes abstraites
Exemple 2 — Traitement polymorphe d'un tableau de formes
Forme[] formes = {
new Cercle("Rouge", 5.0),
new Rectangle("Bleu", 3.0, 7.0),
new Triangle("Vert", 6.0, 8.0, 10.0),
new Cercle("Jaune", 2.5)
};
double surfaceTotale = 0;
for (Forme f : formes) {
surfaceTotale += f.surface();
f.afficher();
}
System.out.printf("%nSurface totale : %.4f%n", surfaceTotale);
Forme : Cercle | Couleur : Rouge | Surface : 78.5398 | Périmètre : 31.4159 Forme : Rectangle | Couleur : Bleu | Surface : 21.0000 | Périmètre : 20.0000 Forme : Triangle | Couleur : Vert | Surface : 24.0000 | Périmètre : 24.0000 Forme : Cercle | Couleur : Jaune | Surface : 19.6350 | Périmètre : 15.7080 Surface totale : 143.1748
5. Méthodes concrètes dans une classe abstraite
Une classe abstraite peut contenir des méthodes concrètes (avec implémentation). Ces méthodes sont héritées par toutes les sous-classes sans devoir être redéfinies, ce qui évite la duplication de code.
Exemple 3 — Méthode concrète partagée
abstract class Animal {
private String nom;
private String espece;
public Animal(String nom, String espece) {
this.nom = nom;
this.espece = espece;
}
public abstract String cri();
public void sePresenter() {
System.out.println("Je m'appelle " + nom + ", je suis un(e) " + espece
+ " et je fais : " + cri());
}
public String getNom() { return nom; }
public String getEspece() { return espece; }
}
class Chien extends Animal {
public Chien(String nom) {
super(nom, "Chien");
}
@Override
public String cri() { return "Ouaf !"; }
}
class Chat extends Animal {
public Chat(String nom) {
super(nom, "Chat");
}
@Override
public String cri() { return "Miaou !"; }
}
class Vache extends Animal {
public Vache(String nom) {
super(nom, "Vache");
}
@Override
public String cri() { return "Meuh !"; }
}
public class TestAnimal {
public static void main(String[] args) {
Animal[] animaux = {
new Chien("Rex"),
new Chat("Minou"),
new Vache("Marguerite")
};
for (Animal a : animaux) {
a.sePresenter();
}
}
}
Je m'appelle Rex, je suis un(e) Chien et je fais : Ouaf ! Je m'appelle Minou, je suis un(e) Chat et je fais : Miaou ! Je m'appelle Marguerite, je suis un(e) Vache et je fais : Meuh !
sePresenter() est définie une seule fois dans Animal. Elle appelle cri() qui est abstraite — c'est l'implémentation de la classe réelle qui est invoquée à l'exécution. Sans les classes abstraites, il faudrait dupliquer sePresenter() dans chaque sous-classe.6. Sous-classe abstraite
Une sous-classe d'une classe abstraite peut elle-même rester abstraite si elle n'implémente pas toutes les méthodes abstraites héritées. C'est utile pour créer des hiérarchies à plusieurs niveaux.
Exemple 4 — Hiérarchie abstraite à plusieurs niveaux
abstract class FormeColoree extends Forme {
private double opacite;
public FormeColoree(String couleur, double opacite) {
super(couleur);
this.opacite = opacite;
}
public double getOpacite() { return opacite; }
public abstract String getDescription();
}
class CercleColore extends FormeColoree {
private double rayon;
public CercleColore(String couleur, double opacite, double rayon) {
super(couleur, opacite);
this.rayon = rayon;
}
@Override public double surface() { return Math.PI * rayon * rayon; }
@Override public double perimetre() { return 2 * Math.PI * rayon; }
@Override public String getDescription() {
return "Cercle coloré de rayon " + rayon + " (opacité : " + getOpacite() + ")";
}
}
7. Pattern Matching avec les classes abstraites (Java 21)
Complément — switch et Pattern Matching (Java 21)
Java 21 permet d'utiliser le pattern matching dans un switch pour traiter différentes sous-classes d'une classe abstraite de façon concise et sûre.
static String decrire(Forme f) {
return switch (f) {
case Cercle c -> String.format("Cercle de rayon %.2f (surface : %.4f)", c.rayon, c.surface());
case Rectangle r -> String.format("Rectangle %sx%s (surface : %.4f)", r.hauteur, r.largeur, r.surface());
case Triangle t -> String.format("Triangle (surface : %.4f)", t.surface());
default -> "Forme inconnue";
};
}
public static void main(String[] args) {
Forme[] formes = { new Cercle("Rouge", 3.0), new Rectangle("Bleu", 4.0, 5.0) };
for (Forme f : formes) {
System.out.println(decrire(f));
}
}
Cercle de rayon 3.00 (surface : 28.2743) Rectangle 4.0x5.0 (surface : 20.0000)
8. Exercice
Hiérarchie de véhicules
Modéliser une hiérarchie de véhicules en utilisant les classes et méthodes abstraites.
Travail demandé
- Créer une classe abstraite
Vehiculeavec :- Attributs :
marque(String),vitesseMax(double). - Méthode abstraite :
double consommation(double distanceKm)— consommation en litres pour une distance donnée. - Méthode concrète :
void afficher(double distanceKm)— affiche la marque, la vitesse max et la consommation calculée.
- Attributs :
- Créer deux sous-classes concrètes :
Voiture(marque, vitesseMax, litresPar100km)— consommation =distance * litresPar100km / 100Moto(marque, vitesseMax, litresPar100km)— consommation =distance * litresPar100km / 100mais 20% de moins qu'une voiture à distance égale
- Tester dans un
mainavec une distance de 250 km.
Marque : Renault | Vmax : 180.0 km/h | Conso (250 km) : 17.50 L Marque : Yamaha | Vmax : 220.0 km/h | Conso (250 km) : 8.00 L
abstract class Vehicule {
private String marque;
private double vitesseMax;
public Vehicule(String marque, double vitesseMax) {
this.marque = marque;
this.vitesseMax = vitesseMax;
}
public abstract double consommation(double distanceKm);
public void afficher(double distanceKm) {
System.out.printf("Marque : %-10s | Vmax : %5.1f km/h | Conso (%d km) : %5.2f L%n",
marque, vitesseMax, (int) distanceKm, consommation(distanceKm));
}
public String getMarque() { return marque; }
public double getVitesseMax(){ return vitesseMax; }
}
class Voiture extends Vehicule {
private double litresPar100km;
public Voiture(String marque, double vitesseMax, double litresPar100km) {
super(marque, vitesseMax);
this.litresPar100km = litresPar100km;
}
@Override
public double consommation(double distanceKm) {
return distanceKm * litresPar100km / 100;
}
}
class Moto extends Vehicule {
private double litresPar100km;
public Moto(String marque, double vitesseMax, double litresPar100km) {
super(marque, vitesseMax);
this.litresPar100km = litresPar100km;
}
@Override
public double consommation(double distanceKm) {
return distanceKm * litresPar100km / 100 * 0.8;
}
}
public class TestVehicule {
public static void main(String[] args) {
double distance = 250;
Vehicule[] vehicules = {
new Voiture("Renault", 180, 7.0),
new Moto("Yamaha", 220, 4.0)
};
for (Vehicule v : vehicules) {
v.afficher(distance);
}
}
}
Marque : Renault | Vmax : 180.0 km/h | Conso (250 km) : 17.50 L Marque : Yamaha | Vmax : 220.0 km/h | Conso (250 km) : 8.00 L
- Méthode abstraite
consommation(): chaque sous-classe fournit sa propre formule — c'est le polymorphisme. La classeVehiculene connaît pas les détails. - Méthode concrète
afficher(): définie une fois dansVehicule, elle appelleconsommation()qui se résout dynamiquement selon le type réel de l'objet. - Tableau
Vehicule[]: le tableau est déclaré du type abstraitVehiculemais contient des instances concrètes — la bouclefor-eachappelle la bonne implémentation deconsommation()dans chaque cas.
L'essentiel en bref
Une classe abstraite (mot-clé abstract) est trop générale pour être instanciée directement — elle sert uniquement de modèle pour l'héritage. Elle peut contenir des méthodes abstraites (sans corps, terminées par ;) que chaque sous-classe concrète doit impérativement implémenter, et des méthodes concrètes héritées telles quelles pour éviter la duplication de code. Toute classe contenant au moins une méthode abstraite doit être déclarée abstract. Une sous-classe qui n'implémente pas toutes les méthodes abstraites héritées doit elle-même rester abstract. Les classes abstraites constituent la base du polymorphisme dynamique : une variable du type abstrait peut référencer n'importe quelle instance d'une sous-classe, et c'est l'implémentation de la classe réelle qui est invoquée à l'exécution.
Discussion (0)
Soyez le premier à laisser un commentaire !
Laisser un commentaire
Votre commentaire sera visible après modération.