Polymorphisme

Ce document présente les concepts fondamentaux du polymorphisme en programmation orientée objet, particulièrement en Java. Il s’adresse aux étudiants et développeurs souhaitant comprendre comment manipuler des objets de différentes classes liées par héritage, en exploitant les mécanismes de surclassement, ligature dynamique, conversion explicite et gestion des arguments effectifs.

D'après le document Polymorphisme

Cet article a été rédigé automatiquement à partir du document source, puis vérifié avant publication.

Document source

Polymorphisme

Programmation Orientée Objet (POO) · DOCX · 1 pages

Consulter le document original →

Ce document présente les concepts fondamentaux du polymorphisme en programmation orientée objet, particulièrement en Java. Il s’adresse aux étudiants et développeurs souhaitant comprendre comment manipuler des objets de différentes classes liées par héritage, en exploitant les mécanismes de surclassement, ligature dynamique, conversion explicite et gestion des arguments effectifs.

Concept de polymorphisme

Le polymorphisme désigne la capacité d’un élément à prendre plusieurs formes. C’est la troisième caractéristique essentielle de la programmation orientée objet, après l’encapsulation et l’héritage. Il permet de manipuler des objets sans connaître précisément leur type, en se basant sur la relation d’héritage.

En Java, le polymorphisme se traduit par :

  • Le surclassement (upcasting) : la compatibilité ascendante entre un type dérivé et un type ascendant.
  • La ligature dynamique : la méthode appelée est déterminée à l’exécution selon le type réel de l’objet, sans avoir besoin de tests explicites.

Un objet possède non seulement son type de classe, mais aussi tous les types de ses classes ancêtres. En cas de redéfinition et surcharge simultanées d’une méthode dans une hiérarchie, les règles de polymorphisme deviennent plus complexes.

Surclassement (upcasting)

Une classe B qui hérite de la classe A peut être vue comme un sous-type de A. Ainsi, un objet de type B peut être manipulé comme un objet de type A. Cette relation est supportée directement en Java :

class A {
  void m1() { System.out.println("Objet de classe A"); }
}

class B extends A {
  void m2() { System.out.println("Objet de classe B"); }
}

A a = new B();
a.m1(); // affiche "Objet de classe A"

Dans cet exemple, la référence a est de type A mais pointe vers un objet de type B. Cela illustre la compatibilité ascendante : on manipule un objet B via une référence de type A.

Plus généralement, une référence d’un type donné peut référencer un objet dont le type effectif est n’importe quelle sous-classe directe ou indirecte.

Attention : lorsqu’un objet est surclassé, il est vu comme un objet du type de la référence utilisée, ce qui restreint l’accès aux fonctionnalités de la classe de la référence :

A a = new B();
a.m1(); // OK
a.m2(); // Erreur : m2() n'existe pas dans A

Exemple avec des classes plus concrètes :

EtudiantSportif es = new EtudiantSportif("DUPONT", "Jean", 25, "Badminton");
Etudiant e;
e = es; // surclassement (upcasting)
es.bonusSportif(); // OK
e.bonusSportif();  // Erreur : e n'a accès qu'aux méthodes de Etudiant

Ligature dynamique

La ligature dynamique (ou liaison tardive) signifie que la méthode appelée est déterminée à l’exécution en fonction du type réel de l’objet, et non du type de la référence.

Ce mécanisme permet d’appeler la version redéfinie d’une méthode dans une classe dérivée, même si l’objet est manipulé via une référence de la classe mère.

Exemple :

public class Etudiant {
  String nom, prénom;
  int age;

  public Etudiant(String nom, String prénom, int age) {
    this.nom = nom;
    this.prénom = prénom;
    this.age = age;
  }

  public void affiche() {
    System.out.println("je suis étudiant");
  }
}

public class EtudiantSportif extends Etudiant {
  String sport;

  public EtudiantSportif(String nom, String prénom, int age, String sp) {
    super(nom, prénom, age);
    sport = sp;
  }

  public void affiche() {
    super.affiche();
    System.out.println("je suis sportif, je pratique le " + this.sport);
  }
}

Etudiant e = new EtudiantSportif("Somrani", "Mohamed", 20, "foot");
e.affiche();

Résultat :

je suis étudiant
je suis sportif, je pratique le foot

La méthode affiche() appelée est celle de la classe effective de l’objet (EtudiantSportif), même si la référence est de type Etudiant.

Application du polymorphisme

Exemple avec des formes géométriques

class graphique {
  private int x, y;

  public graphique(int x, int y) {
    this.x = x;
    this.y = y;
  }

  public void identifie() {
    System.out.println("Je suis une forme géometrique");
  }

  public void affiche() {
    this.identifie();
    System.out.println("Le centre de l’objet se trouve dans : " + x + " et " + y);
  }

  double surface() { return 0; }
}

class Cercle extends graphique {
  private double rayon = 1;

  public Cercle(int x, int y, double r) {
    super(x, y);
    rayon = r;
  }

  public void identifie() {
    System.out.println("Je suis un cercle");
  }

  double surface() {
    return rayon * rayon * 3.14;
  }
}

class Rectangle extends graphique {
  private int larg, longueur;

  Rectangle(int x, int y, int l1, int l2) {
    super(x, y);
    longueur = l1;
    larg = l2;
  }

  double surface() {
    return longueur * larg;
  }

  public void identifie() {
    System.out.println("Je suis un rectangle");
  }
}

class test_poly {
  public static void main(String[] args) {
    graphique g = new graphique(3, 7);
    g.identifie();

    g = new Cercle(4, 8, 10);
    g.identifie();

    g = new Rectangle(7, 9, 10, 3);
    g.identifie();
  }
}

Résultat de l’exécution :

  • Je suis une forme géometrique
  • Je suis un cercle
  • Je suis un rectangle

Ce programme montre que la même variable g peut référencer des objets de classes différentes, et que la méthode appelée dépend du type effectif de l’objet.

Exemple 2 : Tableau hétérogène d’objets

class test_poly2 {
  public static void main(String[] args) {
    graphique[] tab = new graphique[6];

    tab[0] = new graphique(3, 2);
    tab[1] = new Cercle(10, 7, 3);
    tab[2] = new Rectangle(4, 7, 8, 6);
    tab[3] = new graphique(8, 10);
    tab[4] = new Cercle(8, 5, 3);
    tab[5] = new Rectangle(10, 17, 3, 8);

    for (int i = 0; i <= 5; i++) {
      tab[i].identifie();
    }
  }
}

Résultat :

  • Je suis une forme géométrique
  • Je suis un cercle
  • Je suis un rectangle
  • Je suis une forme géométrique
  • Je suis un cercle
  • Je suis un rectangle

Ce tableau illustre la puissance du polymorphisme : il permet de stocker dans une même collection des objets de types différents liés par héritage.

Exemple 3 : Choix dynamique selon un argument

class test_poly3 {
  public static void main(String[] args) {
    graphique d;
    int c = Integer.parseInt(args[0]);

    switch (c) {
      case 0: d = new Cercle(10, 10, 10); break;
      case 1: d = new Rectangle(10, 10, 10, 10); break;
      case 2: d = new graphique(10, 10); break;
      default: d = new graphique(0, 0);
    }

    d.identifie();
    System.out.println("Surface = " + d.surface());
  }
}

Le message affiché dépend de l’argument passé en ligne de commande, démontrant la flexibilité du polymorphisme.

Conversion des arguments effectifs

Une référence à un objet d’un type dérivé peut être passée en paramètre à une méthode qui attend une référence d’un type de base. Cette conversion implicite est légale et ne dégrade pas le polymorphisme.

class A {
  public void identifie() {
    System.out.println("Objet de type A");
  }
}

class B extends A {
  // Pas de redéfinition ici
}

class T {
  static void f(A a) {
    a.identifie();
  }
}

A a = new A();
B b = new B();

T.f(a); // Affiche "Objet de type A"
T.f(b); // Affiche "Objet de type A"

Si la classe B redéfinit la méthode identifie() :

class B extends A {
  public void identifie() {
    System.out.println("Objet de type B");
  }
}

T.f(b); // Affiche "Objet de type B"

La méthode appelée est celle correspondant au type effectif de l’objet, même si la référence est de type de base.

Remarque : Si la méthode f est surchargée, les règles de conversion implicite des paramètres deviennent plus complexes.

Conversions explicites (downcasting)

La conversion inverse, c’est-à-dire d’un type de base vers un type dérivé, n’est pas implicite et doit être réalisée explicitement par un cast. Cette conversion peut échouer à l’exécution si l’objet n’est pas réellement du type dérivé.

graphique g, g1;
Cercle c, c1;

c = new graphique(...); // Erreur de compilation
g = new Cercle(...);    // OK
g1 = new graphique(...); // OK
c1 = new Cercle(...);    // OK
c1 = g1;                // Erreur : conversion implicite illégale
g1 = c1;                // OK

// Conversion explicite nécessaire
c1 = (Cercle) g1;

Cette syntaxe est acceptée à la compilation mais peut provoquer une erreur à l’exécution si la conversion n’est pas valide.

Glossaire des termes clés

  • Polymorphisme : Capacité d’un objet à prendre plusieurs formes, notamment en étant manipulé via des références de types différents dans une hiérarchie d’héritage.
  • Surclassement (upcasting) : Conversion implicite d’une référence d’un type dérivé vers un type de base.
  • Ligature dynamique (liaison tardive) : Résolution des appels de méthodes à l’exécution selon le type réel de l’objet.
  • Conversion explicite (downcasting) : Conversion manuelle d’une référence d’un type de base vers un type dérivé, nécessitant un cast.
  • Redéfinition de méthode : Réécriture d’une méthode dans une classe dérivée pour modifier son comportement.
  • Surcharge de méthode : Définition de plusieurs méthodes portant le même nom mais avec des paramètres différents.
  • Compatibilité ascendante : Possibilité d’affecter une référence d’un type de base à un objet d’un type dérivé.
  • Type effectif : Type réel de l’objet auquel une référence pointe à l’exécution.

Points clés à retenir

  • Le polymorphisme permet de manipuler des objets de différentes classes liées par héritage via des références de type commun.
  • Le surclassement (upcasting) est une conversion implicite et sûre d’un type dérivé vers un type de base.
  • La ligature dynamique garantit que la méthode appelée est celle définie dans la classe réelle de l’objet, même si la référence est de type parent.
  • La conversion explicite (downcasting) est nécessaire pour affecter une référence de type base à une référence de type dérivé, mais elle peut échouer à l’exécution.
  • Le polymorphisme facilite la création de structures de données hétérogènes et la gestion flexible des objets.

Partager

Commentaires

Aucun commentaire pour le moment. Posez la première question.

Les commentaires sont relus avant publication. Votre e-mail n'est jamais affiché.

← Toutes les révisions