<!-- https://informatecdigital.com/fr/Polymorphisme-en-programmation-orient%C3%A9e-objet/ --> Quand le comportement d’un objet dépend de son type : des façons différentes de répondre à la même question. - Polymorphisme ad hoc (**surcharge**) - Polymorphisme par **sous-typage** (**concrétisation** / **redefinition**) - Héritage d'interface - Héritage d'implémentation - Polymorphisme paramétré (types génériques ) ## Polymorphisme ad hoc (surcharge) Le polymorphisme ad hoc (ou surcharge, ou « overload ») permet l’implémentation de fonctions **avec le même nom** pouvant prendre **des paramètres de type différents**.  L’utilisation de la fonction est différente en fonction du type passé en paramètre. ### Surcharge de méthode La surcharge (overloading) d'une méthode : plusieurs signatures pour une méthode portant le même nom. Autrement dit, une méthode portant le même nom avec des paramètres différents. > [!example] Surcharge de la méthode `valueOf()` de String > ```java > class Integer{ > > public static String valueOf(Object obj) { > return (obj == null) ? "null" : obj.toString(); > } > public static String valueOf(char[] data) { > return new String(data); > } > public static String valueOf(boolean b) { > return b ? "true" : "false"; > } > // ... > } > ``` > [!tip] La surcharge est également possible pour les constructeurs ! Le type, le nombre ou l'ordre des paramètres doit changer. ```java class Calculatrice { public int additionner(int a, int b) { return a + b; } public double additionner(double a, double b) { return a + b; } } ``` Ici, la méthode additionner est définie pour deux types de paramètres différents (int et double). > [!warning] le nom des paramètres est ignoré. > > > ```java > void afficher(String nom){} > > // ne compile pas car afficher(String) est considéré comme la même signature > void afficher(String adresse){} > ``` > [!tip] Envelopper dans un type > En enveloppant la valeur dans un type, on peut indiquer au compilateur que `nom` et `Adresse` sont des types différents. > > ```java > void afficher(String nom){} > > record Adresse(String value){} > > void afficher(Adresse adresse){} > ``` ### Paramètres variables pour une méthode Il est possible de surcharger une methode en lui donnant un nombre arbitraire de paramètres. `String... names` est équivalent à `String[] names`. Et permet de d'appeler une méthode ainsi. ```java public static String greet(String name) { return "Hello " + name + "!"; } // Arguments variables public static String greet(String... names) { if (names.length == 0) { return greet("World"); } return "Salut " + String.join(", ", names) + "!"; } ``` Que donnera l'appel suivant ? ```java IO.println(greet()); IO.println(greet("Toi")); // On peut appeler un nombre arbitraire d'arguments IO.println(greet("You", "Me", "Everybody")); ``` > [!note]- Réponse > ```text > Hello World! > Hello Toi! > Salut You, Me, Everybody! > ``` ## Polymorphisme par sous-typage Le **sous-typage** permet de transmettre la **signature** de type à des sous-types. On peut ainsi **invoquer une méthode** avec des paramètres dont les types sont des **sous-types** des types attendus. Ex. on définit un type parent pouvant ajouter un cadeau et retirer l'ensemble des cadeaux ```java public interface Conteneur{ void ajouterCadeau(String cadeau); Iterable<String> retirerCadeaux(); } ``` On crée des variantes de conteneur : Sac ```java public class Sac implements Conteneur{ @Override void ajouterCadeau(String cadeau){ // ... } @Override Iterable<String> retirerCadeaux(){ // ... } } ``` Hotte ```java public class Hotte implements Conteneur{ @Override void ajouterCadeau(String cadeau){ // ... } @Override Iterable<String> retirerCadeaux(){ // ... } } ``` Le traineau du pere noel peut être chargé depuis des conteneurs (Sac, Hotte, ou tout autre sous-type de Conteneur). ```java public class TraineauDuPereNoel{ private List<String> stockage = new ArrayList<String>(); public void charger(Conteneur conteneur){ stockage.addAll(conteneur.retirerCadeaux()); } } ``` ```java public class Main { public static void main(){ Sac sac = new Sac(); sac.ajouterCadeau("Poupée"); sac.ajouterCadeau("Camion"); sac.ajouterCadeau("Pokedex"); Hotte hotte = new Hotte(); hotte.ajouterCadeau("Playstation"); hotte.ajouterCadeau("Lego"); hotte.ajouterCadeau("Livre"); var traineau = new TraineauDuPereNoel(); traineau.charger(sac); traineau.charger(hotte); } } ``` ### Sous-typage par interface Une interface décrit la signatures de méthodes publiques que devront implémenter les classes qui la réalisent. Une interface ne décrit pas les données ou les mécanismes internes de l'implémentation. ```mermaid classDiagram direction BT class Forme <<interface>> Forme Forme: + aire() float class Carre { - cote : float + aire() float } Carre ..|> Forme class Cercle{ - rayon : float + aire() float } Cercle ..|> Forme ``` ```java public interface Forme { float aire(); } ``` Quand une classe implémente une interface, elle **doit** implémenter les comportements définis dans l'interface. Leur concrétisation se fait par le mécanisme de redéfinition (override) de java. ```java public class Carre implements Forme { private final float cote; public Carre(float cote) { this.cote = cote; } @Override public float aire() { return cote * cote; } } public class Cercle implements Forme { private final float rayon; public Cercle(float rayon) { this.rayon = rayon; } @Override public float aire() { return (float) Math.PI * rayon * rayon; } } ``` ```java public interface Nageur{ void nager(); } public class Canard implemets Nageur { //… } public class CanardColVert is extends Canard { //… } ``` ```java Nageur n1 = new Canard(); Nageur n2 = new CanardColVert(); n2.nager(); n1.nager(); ``` Voir [[Héritage de type]] ### Sous-typage par héritage d'implémentation En plus de permettre de transmettre des **comportements** et des **données**, l'héritage d'implémentation transmet également **ses types**. ```mermaid classDiagram class A{ data : String + foo() String } class B{ data : String + bar() String + foo() String } A <|-- B ``` ```java class A{ protected String data="data"; String foo(){ return "Foo" }; } class B extends A { String bar(){ return super.foo().toUpperCase(); } } ``` ```java A a = new A() a.foo(); B b = new B() b.bar(); b.foo(); A[] array = { a, b}; for( item : array){ item.foo(); } ``` Voir [[Héritages en Java]] #### Classes abstraites Si une **[[Glossaire général#Classe abstraite|classe abstraite]]** est **partiellement implémentée** et ne **peut pas être instanciée**, son type peut être utilisé dans la déclaration de variable. Les méthodes abstraites doivent être concrétisées par le mécanisme de redéfinition (override) de java. ```java public abstract class Forme { // ... } public class Carre extends Forme { // ... } public class Cercle extends Forme { // ... } ``` ```java Forme[] tableau = { new Carre(), new Cercle(), new Carre() }; ``` Voir [[Glossaire général#Héritage d’implémentation]] ## Polymorphisme paramétré Permet de définir des traitements qui peuvent être appliqués à des **types paramétrés** (ou "[[Glossaire général#Type générique|types génériques]]" en Java). ```mermaid classDiagram class List~T~{ add(~T~ item) } ``` En Java les types paramétrés sont appelés "[Generic Types](https://dev.java/learn/generics/)" > [!example] `List<T>` en Java > En Java, les collections sont des types génériques. Les opérations sur des instances de collections peuvent s'appliquer quel que soit le type de données qu'elle contiennent. Le paramètre de type de la collection doit être donné lors de sa définition : > > ```java > List<String> strings = new ArrayList<String>(); > strings.add("Foo"); > strings.add("Bar"); > > List<Integer> ints = new ArrayList<Integer>(); > ints.add(1); > ints.add(2); > ``` Les types génériques sont un concept avancé qui ne sera pas approfondi dans ce cours. ## À suivre [[Gestion des dépendances et déploiement]]