1. Principes

Le polymorphisme est un principe qui permet à une variable objet de prendre différentes “formes” au cours de l’exécution, c.a.d référencer des instances de classes différentes, MAIS sous contraintes :

  1. On déclare une telle variable avec un type de base (par ex. A)
  2. On lui assigne des instances de 1 ou de sous-classes de A

Sinon, il y aura une erreur de compilation

class A{}, class B extends A{}, class C extends A{}, class D extends B{},
A a = new A(); // OK
A a = new B(); // OK
A a = new C(); // OK
B b = new A(); // ERR
B b = new B(); // OK
B b = new C(); // ERR
C c = new D(); // ERR
C c = new B(); // ERR
C c = new C(); // OK

D d = new D();
a = d; // OK car D hérite de B qui hérite de A
b = d; // OK car D hérité de B
c = d; // ERR (ce qui est a droite n'hérite pas de ce qui est à gauche)

2. Utilité

Généralement, la polymorphie n’a pas d’intérêt propre et ne trouve son utilité qu’en conjonction avec la redéfinition.

Imaginons un système de gestion de formes géométriques. Nous allons créer une classe de base Forme et quelques sous-classes comme Cercle et Rectangle. Chaque forme aura une méthode pour calculer sa surface, mais cette méthode sera implémentée différemment pour chaque type de forme.

// Classe de base
abstract class Forme {
    abstract double calculerSurface();
    
    void afficherSurface() {
        System.out.println("La surface est : " + calculerSurface());
    }
}

// Sous-classe Cercle
class Cercle extends Forme {
    private double rayon;
    
    public Cercle(double rayon) {
        this.rayon = rayon;
    }
    
    @Override
    double calculerSurface() {
        return Math.PI * rayon * rayon;
    }
}

// Sous-classe Rectangle
class Rectangle extends Forme {
    private double longueur;
    private double largeur;
    
    public Rectangle(double longueur, double largeur) {
        this.longueur = longueur;
        this.largeur = largeur;
    }
    
    @Override
    double calculerSurface() {
        return longueur * largeur;
    }
}

// Classe principale pour tester
public class TestFormes {
    public static void main(String[] args) {
        // Utilisation du polymorphisme
        Forme[] formes = new Forme[3];
        formes[0] = new Cercle(5);
        formes[1] = new Rectangle(4, 6);
        formes[2] = new Cercle(3);
        
        // Calcul et affichage des surfaces
        for (Forme forme : formes) {
            forme.afficherSurface();
        }
    }
}

Dans cet exemple :

  1. Nous avons une classe abstraite Forme avec une méthode abstraite calculerSurface() et une méthode concrète afficherSurface().
  2. Les classes Cercle et Rectangle héritent de Forme et redéfinissent la méthode calculerSurface() selon leurs propres caractéristiques.
  3. Dans la méthode main(), nous utilisons le polymorphisme en créant un tableau de Forme qui peut contenir des instances de Cercle et de Rectangle.
  4. Nous parcourons ce tableau et appelons la méthode afficherSurface() sur chaque élément.

L'utilité du polymorphisme ici est multiple :