Menu

Le polymorphisme en Java : une interface, plusieurs formes

Comment le polymorphisme en Java permet à une variable de référencer plusieurs types, pourquoi les méthodes redéfinies sont résolues à l'exécution, et comment utiliser l'upcasting, le downcasting et instanceof en toute sécurité.

Cette page contient des éditeurs exécutables - modifiez, exécutez et voyez la sortie instantanément.

Une référence, plusieurs types

Le polymorphisme est la récompense de l'héritage et des interfaces. Il signifie qu'une seule variable typée comme un parent (ou une interface) peut contenir un objet de n'importe quel sous-type, et lorsque vous appelez une méthode dessus, Java exécute la version appartenant à la classe réelle de l'objet - et non la version impliquée par le type déclaré de la variable.

Vous avez déjà vu le principe sur la page sur l'héritage : une sous-classe redéfinit une méthode de son parent. Le polymorphisme est ce qui rend cette redéfinition utile - il vous permet d'écrire du code contre le type général et de laisser chaque objet concret se comporter à sa manière.

a et b sont tous deux déclarés comme Animal, pourtant chacun affiche son propre son. Ce choix se fait à l'exécution en fonction de l'objet réel.

La répartition dynamique des méthodes

Le mécanisme sous-jacent est la répartition dynamique des méthodes (dynamic method dispatch) : pour une méthode d'instance redéfinie, la JVM examine la classe à l'exécution de l'objet pour décider quelle implémentation appeler. Le compilateur vérifie seulement que la méthode existe sur le type déclaré ; la sélection réelle est différée jusqu'à l'exécution du programme.

C'est ce qui permet à une seule boucle de gérer tout un mélange de types sans jamais demander ce qu'est chacun d'eux :

La boucle ne connaît que Shape. Ajoutez un Triangle extends Shape plus tard et ce code continue de fonctionner sans modification - c'est tout l'intérêt. Le code dépend de l'abstraction, pas de la liste concrète des types.

Upcasting et downcasting

Stocker un Dog dans une variable Animal est de l'upcasting - remonter la hiérarchie vers un type plus général. C'est toujours sûr et Java le fait implicitement, car tout Dog est un Animal.

Aller dans l'autre sens est du downcasting - prendre une référence parent et la traiter comme un sous-type spécifique. Cela n'est valide que si l'objet est réellement de ce sous-type, vous devez donc écrire le cast explicitement, et vous risquez une ClassCastException si vous vous trompez :

Le dernier cast compile sans problème - le compilateur ne peut pas prouver qu'il est faux - mais il explose à l'exécution car un Cat n'est pas un Dog. Ne faites jamais de downcast à l'aveugle.

Protéger les downcasts avec instanceof

Avant de faire du downcasting, vérifiez le type réel avec instanceof. Le Java moderne vous permet de lier le résultat dans la même expression (filtrage par motif pour instanceof), ce qui vous évite le cast séparé :

instanceof renvoie false pour null, donc la vérification vous protège aussi d'une NullPointerException. Cela dit, si vous vous surprenez à écrire de longues chaînes de instanceof, c'est souvent le signe que le comportement devrait se trouver à l'intérieur des classes sous forme de méthode redéfinie - laissez le polymorphisme gérer le branchement à votre place.

Overriding vs overloading

Ces deux termes se ressemblent mais n'ont aucun rapport, et les confondre est une source classique de confusion.

L'overriding (redéfinition) est une sous-classe qui remplace une méthode du parent ayant la signature identique. Il est résolu à l'exécution selon le type de l'objet - c'est le polymorphisme que nous utilisons.

L'overloading (surcharge) est une même classe ayant plusieurs méthodes du même nom mais avec des listes de paramètres différentes. Il est résolu à la compilation selon les types des arguments, sans aucune répartition à l'exécution :

Le compilateur choisit le describe correspondant uniquement à partir du type statique de l'argument. Aucun objet parent/enfant n'est impliqué, donc ce n'est pas du polymorphisme à l'exécution - cela réutilise simplement un nom de méthode.

Un piège courant : les champs ne sont pas polymorphes

Seules les méthodes d'instance sont réparties à l'exécution. Les champs et les méthodes statiques sont résolus selon le type déclaré, ce qui en piège beaucoup :

p.name() exécute la version de Child (polymorphisme), mais p.label lit le champ de Parent, car les champs sont masqués, pas redéfinis. La solution est simple : gardez les champs private et accédez-y uniquement via des méthodes, pour que l'appel polymorphe l'emporte toujours.

Suite : les modificateurs d'accès

Le polymorphisme ne fonctionne proprement que lorsque les sous-classes peuvent voir et redéfinir les bons membres tandis que le reste de votre code ne peut pas s'y immiscer et briser les invariants. Cet équilibre est contrôlé par les accès public, protected, private et de paquet - les modificateurs d'accès, juste après.

Questions fréquentes

Qu'est-ce que le polymorphisme en Java ?

Le polymorphisme signifie qu'un type de référence peut pointer vers des objets de plusieurs classes différentes, et que la méthode réellement exécutée est choisie selon le type réel de l'objet à l'exécution, et non selon le type déclaré de la variable. Ainsi, une variable Shape shape peut contenir un Circle ou un Square, et appeler shape.area() exécute automatiquement la bonne version.

Quelle est la différence entre overriding et overloading en Java ?

L'overriding (redéfinition) consiste pour une sous-classe à remplacer une méthode de la super-classe ayant la même signature - c'est ce qui rend possible le polymorphisme à l'exécution. L'overloading (surcharge) consiste pour une même classe à avoir plusieurs méthodes portant le même nom mais avec des listes de paramètres différentes ; le compilateur en choisit une à la compilation en fonction des arguments. La redéfinition est résolue à l'exécution selon le type de l'objet ; la surcharge est résolue à la compilation selon les types des arguments.

Quelle est la différence entre upcasting et downcasting en Java ?

L'upcasting traite un objet enfant comme son type parent (Animal a = new Dog();) - toujours sûr et généralement implicite. Le downcasting va dans l'autre sens (Dog d = (Dog) a;) et n'est sûr que si l'objet est réellement de ce sous-type ; sinon il lève une ClassCastException. Protégez chaque downcast avec instanceof au préalable.

Coddy programming languages illustration

Apprendre à coder avec Coddy

COMMENCER