UP | HOME

Annexe Java - Interfaces - Résolution des conflits

Une interface contient des déclarations de méthodes soit héritées soit directement agrégées. Il existe certaines règles à respecter pour garantir le succès de la résolution d'un nom de méthode pour une interface: lors de la compilation, il doit toujours être possible d'associer à un appel de méthode la déclaration d'une méthode correspondante dans l'interface qui est le type de l'objet cible. De manière générale, en Java, une méthode est identifiée par son nom et le type de ses paramètres. Le type de retour n'intervient pas : c'est un principe généralement adopté, en Java comme en C++ par exemple, car il facilite l'analyse du code. En effet, pour analyser un appel de méthode x.m(args), on dispose du nom de la méthode m, du type de l'objet cible référencé par x, et du type des arguments args, mais pas du type de retour, qui lui est déduit de la déclaration de la méthode m, une fois trouvée. N'intervient pas non plus l'interface d'origine, c'est-à-dire l'endroit où cette méthode a été déclarée initialement. C'est une particularité du langage Java ; en C++, ce principe n'est pas suivi par exemple. On dit que deux méthodes sont semblables si elles ne peuvent être distinguées suivant le principe d'identification adopté, autrement dit en Java, si elles ont le même nom et le même type pour leurs paramètres. Une règle simple est nécessaire pour garantir que tout nom de méthode peut être résolu :

dans une interface, il n'existe pas deux méthodes semblables.

Passons en revue les différents cas possibles pour la mise en œuvre de cette règle. Supposons qu'une interface possède deux méthodes semblables. Si les méthodes ont été déclarées dans la même interface, une erreur à la compilation se produit : il s'agit d'un cas manifeste de doublon. Considérons donc le cas où ces deux méthodes ont été déclarées dans des interfaces distinctes. Si ces méthodes sont égales, autrement dit, si elles ont le même type de retour, elles sont fusionnées à la compilation. Supposons donc qu'elles n'ont pas le même type de retour. Dans ce cas, une erreur à la compilation se produit : elles ne peuvent être fusionnées. Cependant, il y a une exception lorsqu'une des deux méthodes spécialise l'autre. On dit qu'une méthode spécialise une autre méthode si elles sont semblables et si le type de retour de la première est un sous-type (strict ou non ; dans ce dernier cas, les deux méthodes sont égales) du type de retour de la seconde. Par exemple, étant donné un type A et un sous-type B de A, la méthode B f() spécialise la méthode A f(). Un exemple typique de spécialisation est celui des fabriques.

interface A {
  ...
  A creer();
}
interface B extends A {
  ...
  B creer(); // -> spécialisation
}

La méthode spécialisant peut toujours remplacer la méthode spécialisée sans provoquer d'erreurs de types : en effet, elle possède le même type pour chacun des paramètres et son type de retour étant un sous-type, par la règle de subsomption, son résultat peut être converti dans le type de retour de la méthode spécialisée. Revenons au cas qui nous intéresse et à l'exception par spécialisation : toute méthode spécialisant remplace la méthode spécialisée, si elle ne provient pas d'une interface parente de l'interface dont provient la méthode spécialisée, sinon, une erreur à la compilation survient.

Enfin, à partir de Java 8, il est possible d'implémenter les méthodes déclarées dans une interface. Dans le cas d'une fusion sans spécialisation, si l'une des méthodes est implémentée, alors une erreur survient : il est nécessaire de redéfinir cette méthode pour lever l'ambiguïté. Pour accéder à la méthode f de X dans sa redéfinition, on utilise la notation X.super.f. Dans le cas d'une spécialisation, la méthode spécialisant remplace la méthode spécialisée : ainsi l'implémentation spécialisante remplace l'implémentation spécialisée.

Voici des exemples illustrant les différents cas possibles. On suppose que B est un sous type de A.

/* Cas 1 : erreur à la compilation */
interface C {
  ... f();
  ... f();
}

/* Cas 2 : spécialisation */
interface D {
  void f();
}
interface E extends D {
  void f();
}

/* Cas 3 : fusion sans spécialisation */
interface F {
  void f();
}
interface G {
  void f();
} 
interface H extends F, G {}

/* Cas 4 : erreur à la compilation */
interface I {
  B f(); // méthode spécialisant
}
interface J extends I {
  A f(); // méthode spécialisée
}

/* Cas 5 : spécialisation réussie */
interface K {
  A f(); // méthode spécialisée
}
interface L extends K {
  B f(); // méthode spécialisant
}

/* Cas 6 : spécialisation réussie */
interface M {
  A f(); // méthode spécialisée
}
interface N {
  B f(); // méthode spécialisant
}
interface P extends M, N {}

Il est possible pour une interface de contenir deux méthodes non semblables mais ayant le même nom. On dit alors que la méthode est surchargée. Elles se distinguent alors par le type de leurs paramètres : ou bien elles n'ont pas le même nombre de paramètres, ou bien elles ont le même nombre de paramètres, mais le type d'au moins un des paramètres diffère, d'une méthode à l'autre.

Author: Hervé Grall
Version history: v1: 2017-09-26.
Comments or questions: Send a mail.
The webpage content is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.