Comprendre le polymorphisme
Le polymorphisme est un concept central en biologie et en informatique, mais il prend des significations légèrement différentes dans chacun de ces domaines. Pour le rendre plus accessible, on peut le comparer à quelque chose de très quotidien : les outils multifonctions.
En biologie
En biologie, le polymorphisme se réfère à la coexistence de plusieurs formes différentes d'une même espèce au sein d'une même population. C'est un peu comme si différents outils étaient disponibles pour réaliser une tâche selon les conditions : une pelle pour creuser ou une fourche pour aérer la terre, selon ce que le jardinier juge le plus approprié. Ces variations peuvent concerner la couleur, la forme, la taille ou d'autres caractéristiques physiques, et elles sont généralement le résultat de variations génétiques.
Pourquoi la nature fait-elle cela ? La réponse est la survie. Le polymorphisme peut aider une espèce à mieux s'adapter à un environnement changeant. Par exemple, certains papillons ont développé différents motifs de couleurs pour soit se camoufler et éviter les prédateurs, soit imiter l'apparence d'autres espèces plus toxiques.

En informatique
En informatique, le polymorphisme est un principe de la programmation orientée objet qui permet à des objets de différents types de traiter des données de manière différente, tout en partageant le même nom d'interface. Imaginez un appareil électronique universel qui peut agir comme téléphone, calculatrice ou appareil photo selon le bouton que vous appuyez.
Le polymorphisme permet donc à une fonction, un objet ou une méthode de se comporter différemment selon le contexte d'utilisation, sans que le programmeur ait besoin de connaître tous les détails techniques de chaque objet. Cela simplifie énormément la programmation en permettant de réutiliser des morceaux de code de manière flexible.

Conclusion
Que ce soit dans le monde naturel ou dans le code informatique, le polymorphisme est une stratégie d'adaptation et d'efficacité. En biologie, il aide les espèces à survivre et à prospérer dans des environnements variés. En informatique, il rend les programmes plus flexibles et plus faciles à développer et à maintenir. Dans les deux cas, c’est une belle illustration de la manière dont un seul concept peut prendre différentes formes pour répondre efficacement à divers besoins.
Le polymorphisme est un concept fondamental de la programmation orientée objet (POO) qui permet à une classe d’hériter des comportements d’une autre classe. En Java, le polymorphisme permet à une classe d’être traitée comme une autre classe, généralement une superclasse. Il existe deux types de polymorphisme en Java : le polymorphisme statique (ou surcharge) et le polymorphisme dynamique (ou substitution).
- Polymorphisme statique (Surcharge) : Il se produit lorsque deux ou plusieurs méthodes dans la même classe ont le même nom mais des paramètres différents. C’est ce qu’on appelle aussi la surcharge de méthode. De même, les constructeurs peuvent aussi être surchargés en Java, ce qui est connu sous le nom de surcharge de constructeur.
public class Calcul {
// méthode pour additionner deux entiers
public int additionner(int a, int b) {
return a + b;
}
// méthode surchargée pour additionner trois entiers
public int additionner(int a, int b, int c) {
return a + b + c;
}
}
- Polymorphisme dynamique (Substitution) : Il se produit lorsque une sous-classe fournit une implémentation spécifique d’une méthode qui est déjà fournie par sa superclasse. C’est ce qu’on appelle aussi la substitution de méthode ou l’overriding de méthode.
public class Animal {
public void faireDuBruit() {
System.out.println("L'animal fait du bruit");
}
}
public class Chien extends Animal {
@Override
public void faireDuBruit() {
System.out.println("Le chien aboie");
}
}
Dans cet exemple, la classe Chien hérite de la classe Animal et fournit sa propre implémentation de la méthode faireDuBruit.

Exemple
Class de l’évolution des baleines depuis le Pakicetus en utilisant le polymorphisme et l’héritage :
Schema simplifié de l'évolution des baleines. L'évolution des baleines est un cas intéressant car c'est le seul animal dont ont connait aujourdhui tous les ancêtres qui commence à la préhistoire jusqu'à aujourdhui.
C'est donc parfait pour illustrer l'héritage.
Voici un schéma échantillon de l'évolution des baleines.
Le code suivant n'est pas fonctionnel dans VS Code. C'est juste un support pour le cour.
Vous pouvez esssayer de le rendre fonctionnel si vous êtes en avance. Sinon une solution se trouve dans le coce suivant
// Classe de base Artiodactyle
public class Artiodactyle {
public void seDeplacer() {
System.out.println("L'artiodactyle marche...");
}
}
// Sous-classe Indohyus héritant de Artiodactyle
public class Indohyus extends Artiodactyle {
@Override
public void seDeplacer() {
System.out.println("L'Indohyus marche et nage occasionnellement...");
}
}
// Classe de base Cetace
public class Cetace extends Indohyus {
@Override
public void seDeplacer() {
System.out.println("Le cétacé nage...");
}
}
// Sous-classe Pakicetus héritant de Cetace
public class Pakicetus extends Cetace {
@Override
public void seDeplacer() {
System.out.println("Le Pakicetus marche et nage...");
}
}
// Sous-classe Ambulocetus héritant de Pakicetus
public class Ambulocetus extends Pakicetus {
@Override
public void seDeplacer() {
System.out.println("L'Ambulocetus nage plus efficacement...");
}
}
// Sous-classe Basilosaurus héritant de Ambulocetus
public class Basilosaurus extends Ambulocetus {
@Override
public void seDeplacer() {
System.out.println("Le Basilosaurus est un excellent nageur...");
}
}
// Sous-classe Dorudon héritant de Basilosaurus
public class Dorudon extends Basilosaurus {
@Override
public void seDeplacer() {
System.out.println("Le Dorudon nage gracieusement dans l'océan...");
}
}
// Sous-classe Baleine héritant de Dorudon
public class Baleine extends Dorudon {
@Override
public void seDeplacer() {
System.out.println("La baleine nage majestueusement dans l'océan...");
}
}
// Sous-classe Mysticetes (baleines à fanons) héritant de Baleine
public class Mysticetes extends Baleine {
@Override
public void seDeplacer() {
System.out.println("Les Mysticètes, ou baleines à fanons, nagent tranquillement en filtrant l'eau de mer pour se nourrir...");
}
}
// Sous-classe Rorqual héritant de Mysticetes
public class Rorqual extends Mysticetes {
@Override
public void seDeplacer() {
System.out.println("Le rorqual nage rapidement dans l'océan...");
}
}
// Classe principale pour tester le polymorphisme
public class Main {
public static void main(String[] args) {
// Création d'un objet Rorqual
Artiodactyle monRorqual = new Rorqual();
// Appel de la méthode seDeplacer()
monRoqual.seDeplacer(); // Affiche : Le rorqual nage rapidement dans l'océan...
}
}
Dans cet exemple, chaque classe représente une étape clé dans l’évolution des baleines depuis le Pakicetus. Chaque classe a sa propre implémentation de la méthode seDeplacer(), qui illustre comment ces animaux se déplaçaient à leur époque respective. Lorsque nous créons un objet monArtiodactyle de type Artiodactyle mais instancions cet objet comme un Rorqual, la méthode seDeplacer() du Rorqual est appelée. C’est ce qu’on appelle le polymorphisme.

Class et Sous Class
Attention pratique à utiliser avec parcimonie.
En Java, il est possible de créer une classe qui contient plusieurs sous-classes. Ces sous-classes sont appelées "classes internes" ou "inner classes". Une classe interne est définie à l'intérieur du corps d'une autre classe. Java permet de créer quatre types de classes internes :
-
Classe interne régulière (non statique) : Elle est définie à l'intérieur du corps d'une classe et n'a pas de modificateur
static. -
Classe interne statique : Elle est définie comme une classe interne mais avec le modificateur
static. -
Classe interne locale : Elle est définie à l'intérieur d'une méthode.
-
Classe interne anonyme : Elle est déclarée et instanciée en même temps, généralement lors de la création d'instances d'interfaces ou de sous-classes.
Voici un exemple illustrant une classe contenant plusieurs types de classes internes :
public class OuterClass {
// Attribut de la classe externe
private String outerAttribute = "Je suis la classe externe";
// Classe interne régulière
class InnerClass {
public void display() {
System.out.println(outerAttribute);
}
}
// Classe interne statique
static class StaticInnerClass {
public void display() {
System.out.println("Je suis la classe interne statique");
}
}
public void outerMethod() {
// Classe interne locale
class LocalInnerClass {
public void display() {
System.out.println(outerAttribute);
}
}
LocalInnerClass local = new LocalInnerClass();
local.display();
}
public Runnable getRunnable() {
// Classe interne anonyme
return new Runnable() {
@Override
public void run() {
System.out.println("Je suis la classe interne anonyme");
}
};
}
public static void main(String[] args) {
OuterClass outer = new OuterClass();
// Instanciation de la classe interne régulière
OuterClass.InnerClass inner = outer.new InnerClass();
inner.display();
// Instanciation de la classe interne statique
OuterClass.StaticInnerClass staticInner = new OuterClass.StaticInnerClass();
staticInner.display();
// Utilisation de la classe interne locale
outer.outerMethod();
// Utilisation de la classe interne anonyme
Runnable runnable = outer.getRunnable();
runnable.run();
}
}
L'utilisation de sous-classes (aussi appelées classes dérivées ou classes enfants) en Java, comme dans d'autres langages de programmation orientée objet, dépend du contexte et des besoins spécifiques du projet. Voici quelques points à considérer :

Avantages de l'utilisation des sous-classes :
-
Réutilisation du code : L'héritage permet de réutiliser le code de la classe parente, ce qui peut réduire la redondance.
-
Extensibilité : Les sous-classes peuvent étendre les fonctionnalités de la classe parente sans la modifier.
-
Polymorphisme : L'héritage permet le polymorphisme, où une classe enfant peut être traitée comme une instance de la classe parente. Cela peut simplifier le code et le rendre plus flexible.
-
Organisation logique : Les sous-classes peuvent aider à organiser le code de manière hiérarchique et logique, reflétant des catégories ou des types réels dans le monde.
Inconvénients ou précautions à prendre :
-
Complexité accrue : L'utilisation excessive de l'héritage peut rendre le code plus complexe et difficile à suivre, surtout si la hiérarchie d'héritage est profonde ou compliquée.
-
Couplage fort : Les sous-classes sont étroitement liées à leur classe parente, ce qui signifie qu'un changement dans la classe parente peut affecter toutes ses sous-classes.
-
Surcharge : Il peut être tentant d'utiliser l'héritage pour chaque petite variation d'une classe, ce qui peut entraîner une surcharge de classes et une complexité inutile.
-
Mauvaise utilisation : L'héritage ne doit pas être utilisé simplement pour réutiliser du code. Si deux classes partagent du code mais n'ont pas une relation "est-un", il est peut-être préférable d'utiliser la composition plutôt que l'héritage.
Recommandations :
-
Préférez la composition à l'héritage : Comme le dit le principe de conception, "Préférez la composition à l'héritage". La composition est souvent plus flexible et évite certains des pièges de l'héritage.
-
Utilisez l'héritage judicieusement : L'héritage est puissant, mais il ne doit être utilisé que lorsqu'il y a une véritable relation "est-un" entre la sous-classe et la classe parente.
-
Limitez la profondeur de la hiérarchie d'héritage : Une hiérarchie d'héritage trop profonde peut rendre le code difficile à comprendre et à maintenir.
En conclusion, alors que l'héritage et l'utilisation de sous-classes sont des outils précieux en Java, ils doivent être utilisés judicieusement. Comprendre quand et comment les utiliser efficacement est essentiel pour écrire un code propre, maintenable et efficace.
Code fonctionnel avec les fichiers MesBaleines.java et Main.java pour comprendre l'héritage
MesBaleines.java
public class MesBaleines {
// Classe de base Cetace
public class Cetace {
public void seDeplacer() {
System.out.println("Le cétacé nage...");
}
}
// Classe de base Artiodactyle
public class Artiodactyle {
public void seDeplacer() {
System.out.println("L'artiodactyle marche...");
}
}
// Sous-classe Indohyus héritant de Artiodactyle
public class Indohyus extends Artiodactyle {
@Override
public void seDeplacer() {
System.out.println("L'Indohyus marche et nage occasionnellement...");
}
}
// Sous-classe Pakicetus héritant de Cetace
public class Pakicetus extends Cetace {
@Override
public void seDeplacer() {
System.out.println("Le Pakicetus marche et nage...");
}
}
// Sous-classe Ambulocetus héritant de Pakicetus
public class Ambulocetus extends Pakicetus {
@Override
public void seDeplacer() {
System.out.println("L'Ambulocetus nage plus efficacement...");
}
}
// Sous-classe Basilosaurus héritant de Ambulocetus
public class Basilosaurus extends Ambulocetus {
@Override
public void seDeplacer() {
System.out.println("Le Basilosaurus est un excellent nageur...");
}
}
// Sous-classe Dorudon héritant de Basilosaurus
public class Dorudon extends Basilosaurus {
@Override
public void seDeplacer() {
System.out.println("Le Dorudon nage gracieusement dans l'océan...");
}
}
// Sous-classe Baleine héritant de Dorudon
public class Baleine extends Dorudon {
@Override
public void seDeplacer() {
System.out.println("La baleine nage majestueusement dans l'océan...");
}
}
// Sous-classe Mysticetes (baleines à fanons) héritant de Baleine
public class Mysticetes extends Baleine {
@Override
public void seDeplacer() {
System.out.println("Les Mysticètes, ou baleines à fanons, nagent tranquillement en filtrant l'eau de mer pour se nourrir...");
}
}
// Sous-classe Rorqual héritant de Mysticetes
public class Rorqual extends Mysticetes {
@Override
public void seDeplacer() {
System.out.println("Le rorqual nage rapidement dans l'océan...");
}
}
}
Main.java
public class Main {
/**
* Le code new MesBaleines().new Rorqual();
* est utilisé pour créer une instance de la classe interne Rorqual qui est définie à l'intérieur de la classe externe MesBaleines.
* Voici une explication détaillée :
new MesBaleines() : Ceci crée une nouvelle instance de la classe externe MesBaleines.
.new Rorqual() : Une fois que nous avons une instance de la classe externe, nous utilisons cette instance pour créer une nouvelle instance de la classe interne Rorqual.
En Java, pour instancier une classe interne non statique,
vous devez d'abord avoir une instance de la classe externe.
C'est parce que chaque instance de la classe interne est associée à une instance spécifique de la classe externe.
Cette association permet à la classe interne d'accéder directement aux membres (même privés) de la classe externe.
Dans l'exemple donné, new MesBaleines().new Rorqual();
crée d'abord une instance de MesBaleines, puis utilise cette instance pour créer une instance de la classe interne Rorqual.
C'est une particularité des classes internes non statiques en Java.
Si Rorqual avait été déclaré comme une classe interne statique, vous auriez pu simplement l'instancier avec new MesBaleines.Rorqual();.
Mais dans le code fourni, Rorqual est une classe interne non statique, d'où la nécessité de la syntaxe spécifique.
*/
public static void main(String[] args) {
// Création d'un objet Rorqual
MesBaleines.Rorqual monRorqual = new MesBaleines().new Rorqual();
// Appel de la méthode seDeplacer()
monRorqual.seDeplacer(); // Affiche : Le rorqual nage rapidement dans l'océan...
}
}
Exercice 1
Exercice sur les Classes et l'Héritage en Java: Systèmes de Propulsion des Voitures
Objectif: Développer une hiérarchie de classes en Java pour représenter différents types de voitures selon leur système de propulsion : Diesel, Essence, Électrique, Hydrogène, et à Pédale.
Instructions:
-
Créer une classe de base appelée
Voiturequi contient :- Un attribut
marquede type String. - Un attribut
modelede type String. - Une méthode
demarrer()qui affiche "La voiture démarre". - Une méthode
arreter()qui affiche "La voiture s'arrête".
- Un attribut
-
Créer des classes dérivées pour chaque type de propulsion :
VoitureDieselqui étendVoitureet ajoute une méthoderechargerDiesel()qui affiche "Recharge du réservoir diesel".VoitureEssencequi étendVoitureet ajoute une méthoderechargerEssence()qui affiche "Recharge du réservoir essence".VoitureElectriquequi étendVoitureet ajoute une méthoderechargerBatterie()qui affiche "Recharge de la batterie électrique".VoitureHydrogenequi étendVoitureet ajoute une méthoderechargerHydrogene()qui affiche "Recharge du réservoir d'hydrogène".VoiturePedalequi étendVoitureet modifie les méthodesdemarrer()etarreter()pour afficher respectivement "Pédalez pour démarrer" et "Arrêtez de pédaler pour vous arrêter".
-
Créer une classe
TestVoitures:- Créer des instances de chaque type de voiture.
- Appeler les méthodes
demarrer(),arreter(), et la méthode spécifique de recharge pour chaque type de voiture.
Exemple de code pour la classe Voiture et une classe dérivée:
public class Voiture {
protected String marque;
protected String modele;
public Voiture(String marque, String modele) {
this.marque = marque;
this.modele = modele;
}
public void demarrer() {
System.out.println("La voiture démarre");
}
public void arreter() {
System.out.println("La voiture s'arrête");
}
}
public class VoitureDiesel extends Voiture {
public VoitureDiesel(String marque, String modele) {
super(marque, modele);
}
public void rechargerDiesel() {
System.out.println("Recharge du réservoir diesel");
}
}
Consigne: Utilisez cet exercice pour pratiquer l'héritage, la surcharge des méthodes, et comprendre comment différentes classes peuvent représenter des concepts similaires mais avec des comportements distincts.
N'hésitez pas à ajouter des caractéristiques ou méthodes supplémentaires pour rendre l'exercice plus intéressant et éducatif!
Exercice 2 (pour les Geeks)
Cétacés ; sous-ordre : Mysticètes, regroupant quatre familles : les Eschrichtiidés (baleine grise), les Balaenidés (baleines franches), et les Néobalaenidés (baleine pygmée)
Ajouter les baleines en ajouttant les 3 familles avec le bon héritage en suivant ce schéma.

L'exercice vous demande de modéliser la hiérarchie taxonomique des cétacés, en particulier le sous-ordre des Mysticètes, qui comprend trois familles de baleines. En programmation orientée objet, cette hiérarchie peut être représentée à l'aide de classes et de sous-classes pour illustrer les relations d'héritage.
Voici une explication détaillée de la manière dont vous pourriez structurer cela en Java :
-
Classe Cetace : C'est la classe de base qui représente tous les cétacés. Elle pourrait avoir des attributs et des méthodes communs à tous les cétacés.
- nager() ex: System.out.println("Le cétacé nage...");
-
Sous-classe Mysticetes : Cette classe hérite de
Cetaceet représente le sous-ordre des Mysticètes. Elle pourrait avoir des attributs et des méthodes spécifiques aux Mysticètes.- filtrerEau() : ex: System.out.println("Le mysticète filtre l'eau pour se nourrir...");
- filtrerEau() : ex: System.out.println("Le mysticète filtre l'eau pour se nourrir...");
-
Familles de Mysticètes : Chacune des trois familles serait une sous-classe de
Mysticetes. Ces classes hériteraient des attributs et des méthodes deMysticetes(et, par extension, deCetace).- Eschrichtiidés : Représente la famille des baleines grises.
la méthode nomCommun() affiche Baleines Grises - Balaenidés : Représente la famille des baleines franches.
la méthode nomCommun() affiche baleine franche - Néobalaenidés : Représente la famille des baleines pygmées.
la méthode nomCommun() affiche baleine pygmée
- Eschrichtiidés : Représente la famille des baleines grises.
L'exercice vous demande d'ajouter ces familles en respectant le bon héritage, ce qui signifie que vous devez vous assurer que chaque famille hérite de la classe appropriée pour refléter la hiérarchie taxonomique réelle des baleines.
Vous devez afficher par exemple dans la console.
En créant un instance de Balaenidés et en appelant différente méthodes vous devrez afficher
La "baleine franche" est un cétacé. "Le cétacé nage...". C'est aussi un mysticète. "Le mysticète filtre l'eau pour se nourrir..."
Les partie encadrées par des guillemets sont obtenues par les retours des méthodes System.out.println("Le mysticète filtre l'eau pour se nourrir...");
Le reste est ajouté dans la String qui affiche le résultat final.

