Dans une interview, on m'a demandé si le polymorphisme pouvait être réalisé sans héritage. Est-ce possible ?
Excellente réponse. Ce serait encore mieux si vous la commenciez par un résumé précis et clair pour répondre à la question.
Dans une interview, on m'a demandé si le polymorphisme pouvait être réalisé sans héritage. Est-ce possible ?
La meilleure explication sur le sujet que j'aie jamais lue est un article de Luca Cardelli un théoricien renommé de la typologie. L'article s'intitule Comprendre les types, l'abstraction de données et le polymorphisme .
Cardelli définit plusieurs types de polymorphisme dans cet article :
Le type de polymorphisme lié à l'héritage est classé comme polymorphisme d'inclusion ou polymorphisme de sous-type.
Wikipedia fournit une bonne définition :
Dans la programmation orientée objet, le polymorphisme de sous-type ou l'inclusion polymorphisme est un concept de la théorie des types dans lequel un nom peut dénoter instances de nombreuses classes différentes, à condition qu'elles soient reliées par une super classe commune. Le polymorphisme d'inclusion est généralement généralement supporté par le sous-typage, c'est-à-dire que les objets de types différents sont sont entièrement substituables aux objets d'un autre type (leur(s) type(s) de base). type(s) de base) et peuvent donc être gérés par une interface commune. Le polymorphisme d'inclusion peut également être réalisé par le biais de la coercition de type, également connue sous le nom de type coercition de type, également connue sous le nom de moulage de type.
Un autre article de Wikipedia intitulé Polymorphisme dans la programmation orientée objet semble répondre à vos questions également.
Cette fonctionnalité de sous-typage en Java est réalisée, entre autres, par l'héritage des classes et des interfaces. Bien que les caractéristiques de sous-typage de Java ne soient pas toujours évidentes en termes d'héritage. Prenez par exemple les cas de covariance et de contravariance avec les génériques. De même, les tableaux sont sérialisables et clonables, bien que cela ne soit pas évident dans la hiérarchie des types. On peut également dire que, grâce à la conversion par élargissement primitif, les opérateurs numériques de Java sont polymorphes, acceptant même dans certains cas des opérandes totalement indépendants (par exemple, la concaténation de chaînes de caractères et de nombres ou d'une chaîne de caractères et d'un autre objet). Considérons également les cas de mise en boîte et de mise hors boîte des primitives. Ces derniers cas de polymorphisme (coercition et surcharge) ne sont pas du tout liés à l'héritage.
Inclusion
List<Integer> myInts = new ArrayList<Integer>();
C'est le cas auquel votre question semble faire référence, c'est-à-dire lorsqu'il existe une relation d'héritage ou d'implémentation entre les types, comme dans le cas présent où ArrayList implémente List.
Comme je l'ai mentionné, cependant, lorsque vous introduisez des génériques Java, les règles de sous-typage deviennent parfois floues :
List<? super Number> myObjs = new ArrayList<Object>();
List<? extends Number> myNumbers = new LinkedList<Integer>();
Et dans d'autres cas, les relations ne sont même pas évidentes dans l'API.
Cloneable clone = new int[10];
Serializable obj = new Object[10]
Malgré tout, tous ces éléments, selon Cardelli, sont des formes de polymorphisme universel.
Paramétrique
public <T> List<T> filter(Predicate<T> predicate, List<T> source) {
List<T> result = new ArrayList<>();
for(T item : source) {
if(predicate.evaluate(item)){
result.add(item);
}
return result;
}
}
Le même algorithme peut être utilisé pour filtrer toutes sortes de listes avec toutes sortes de prédicats sans avoir à répéter une seule ligne de code pour chaque type de liste possible. Le type de la liste réelle et le type de prédicat sont paramétriques. Voir cet exemple avec les expressions lambda disponibles dans Aperçu du JDK 8 (pour la brièveté de l'implémentation du prédicat).
filter(x -> x % 2 == 0, asList(1,2,3,4,5,6)); //filters even integers
filter(x -> x % 2 != 0, asList(1L,2L,3L,4L,5L,6L)); //filters odd longs
filter(x -> x >= 0.0, asList(-1.0, 1.0)); //filters positive doubles
Selon Cardelli, il s'agit d'une forme de polymorphisme universel.
Coercition
double sum = 1 + 2.0;
L'arithmétique des nombres entiers et celle des nombres flottants sont totalement différentes. L'application de l'opérateur plus à deux opérandes de types différents est ici impossible sans une forme de coercition.
Dans cet exemple, les types integer et double sont automatiquement convertis en type double sans qu'il soit nécessaire d'effectuer un cast explicite. L'expression entière est promue en double. Il en est ainsi parce qu'en Java, nous avons des conversions d'élargissement primitives.
Selon Cardelli, cette forme de coercition automatique est une forme de polymorphisme ad-hoc prévue pour l'opérateur plus.
Il existe des langages dans lesquels il n'est même pas possible d'additionner un nombre entier et un nombre à virgule flottante sans un cast explicite (par exemple, à ma connaissance, SML, dans lequel, soit dit en passant, le polymorphisme paramétrique est essentiel pour surmonter ce type de problèmes).
Surcharge de travail
double sum = 2.0 + 3.0;
String text = "The sum is" + sum;
L'opérateur plus signifie ici deux choses différentes selon les arguments utilisés. De toute évidence, l'opérateur a été surchargé. Cela implique qu'il a différentes implémentations selon les types d'opérandes. Selon Cardelli, il s'agit d'une forme de polymorphisme ad hoc fourni pour l'opérateur plus.
Bien entendu, cela s'applique également aux formes de surcharge de méthodes dans les classes (par exemple, les méthodes java.lang.Math min et max sont surchargées pour prendre en charge différents types de primitives).
Même si l'héritage joue un rôle important dans la mise en œuvre de certaines de ces formes de polymorphisme, il n'est certainement pas le seul moyen. D'autres langages qui ne sont pas orientés objet fournissent d'autres formes de polymorphisme. Prenons, par exemple, les cas de dactylographie du canard dans des langages dynamiques comme Python ou même dans des langages à typage statique comme Go, ou encore types de données algébriques dans des langages comme SML, Ocaml et Scala, ou encore classes de type dans des langages comme Haskell, méthodes multiples en Clojure, héritage prototypique en JavaScript, etc.
Excellente réponse. Ce serait encore mieux si vous la commenciez par un résumé précis et clair pour répondre à la question.
@RichardSitze J'ai essayé d'obtenir une explication plus détaillée. J'espère ne pas avoir obscurci le sens du message original.
Wow en fait, j'essayais de suggérer une phrase en haut de page pour dire : Oui ou non. Le lecteur a alors une idée de ce qu'il va faire en lisant le reste. Aussi, comme Stephen C a souligné, la réponse peut être différente Java (qui était la question originale), puis pour d'autres langues. Bon travail !
Polymorphisme ad hoc > Surcharge d'opérateurs > Sans hérédité
Polymorphisme ad hoc > Surcharge des méthodes > Sans hérédité
Polymorphisme ad hoc > Chevauchement de méthodes > Avec hérédité
Polymorphisme paramétrique > Génériques > Sans héritage
Polymorphisme de sous-type ou polymorphisme d'inclusion > Affectation polymorphe > Avec hérédité
Polymorphisme de sous-type ou polymorphisme d'inclusion > Type de retour polymorphe > Avec hérédité
Polymorphisme de sous-type ou Polymorphisme d'inclusion > Type d'argument polymorphe > Avec hérédité
Polymorphisme de coercition > Élargissement > Avec ou sans hérédité
Polymorphisme par coercition > Auto boxing et unboxing > Sans hérédité
Polymorphisme de coercition > Var args > Sans hérédité
Polymorphisme de coercition > Casting de type > Sans héritage
Bien sûr. En Java, vous pouvez avoir deux classes qui implémentent la même interface, et leurs résultats sont polymorphes. Aucune fonctionnalité n'est héritée.
public interface Foo {
public int a();
}
public class A implements Foo {
public int a() {
return 5;
}
}
public class B implements Foo {
public int a() {
return 6;
}
}
Puis ailleurs :
Foo x = new A();
System.out.println(x.a())
Foo y = new B();
System.out.println(y.a())
Les deux sites x
y y
sont Foo
mais ils ont des résultats différents lorsque vous appelez a()
.
L'interface est héritée. Le contrat sémantique (jamais inforceble) est implicitement hérité. Voir la réponse d'Edwin.
Bien, c'est juste. J'ai supposé que l'auteur de la question voulait dire "héritage d'implémentation". Sans autre précision de la part de l'enquêteur, et surtout dans le cadre d'un entretien orienté Java, cette réponse est probablement celle qu'ils recherchent. Il existe, bien sûr, des réponses beaucoup plus détaillées.
Oui, je suis d'accord. J'ai tendance à considérer les interfaces (à tort ou à raison) comme un moyen de garantir au compilateur qu'un objet aura une certaine méthode avec une certaine signature, ce qui me fait penser au typage des canards, qui n'implique pas vraiment l'héritage. Techniquement, c'est considéré comme de l'héritage, bien sûr.
La surcharge de fonctions est l'un des polymorphismes (bien qu'il ne s'agisse pas du véritable polymorphisme) qui peut être réalisé sans héritage.
par exemple
class Foo {
public void Arrest( Animal A){
/*code...*/
}
public void Arrest( Terrorist T ) {
/*code...*/
}
}
from main :
Foo f= new Foo();
f.Arrest( new Lion() );
f.Arrest(new Terrorist());
La méthode Arrest est appelée 2 fois mais le chemin d'exécution du code est différent.
*Encore une fois, ce n'est pas une vraie forme de polymorphisme. Le vrai polymorphisme en général ne peut être réalisé sans héritage.
Les langages qui ne sont pas orientés objet fournissent des formes de polymorphisme qui ne s'appuient pas sur l'héritage (i.e. polymorphisme paramétrique ). Cela est en partie possible en Java grâce aux génériques.
Oui, je pense qu'ils voulaient probablement entendre parler de polymorphisme par interfaces. Donc s'il y a 2 classes qui implémentent la même interface, alors on peut l'utiliser dans tous les endroits où l'on attend un objet avec cet intervalle. Voir le code de wikipedia :
// from file Animal.java
public interface Animal {
public String talk();
}
// from file Cat.java
public class Cat implements Animal {
@Override
public String talk() {
return "Cat says Meow!";
}
}
// from file Dog.java
public class Dog implements Animal {
@Override
public String talk() {
return "Dog says Woof! Woof!";
}
}
// from file PolymorphismExample.java
public class PolymorphismExample {
public static void main(String[] args) {
Collection<Animal> animals = new ArrayList<Animal>();
animals.add(new Cat());
animals.add(new Dog());
for (Animal a : animals) {
System.out.println(a.talk());
}
}
}
Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.