Remplacer une méthode en Java de manière générique

Si j’ai une classe de base comme celle-ci, je ne peux pas changer:

public abstract class A { public abstract Object get(int i); } 

et j’essaie de l’étendre avec une classe B comme celle-ci:

 public class B extends A{ @Override public Ssortingng get(int i){ //impl return "SomeSsortingng"; } } 

tout va bien. Mais ma tentative de le rendre plus générique échoue si j’essaie:

 public class C extends A{ @Override public  T get(int i){ //impl return (T)someObj; } } 

Je ne vois aucune raison pour laquelle cela devrait être refusé. À mon sens, le type générique T est lié à un Object qui est le type de retour demandé de A Si je peux AnyObject Ssortingng ou AnyObject comme type de retour dans B , pourquoi ne puis-je pas utiliser T dans ma classe C ?

Un autre comportement étrange, de mon sharepoint vue, est qu’une méthode supplémentaire comme celle-ci:

 public class D extends A{ @Override public Object get(int i){ //impl } public  T get(int i){ //impl } } 

est également interdit, avec le soupçon d’un DuplicateMethod fourni. Celui-ci, au moins, m’embrouille et j’estime que Java devrait prendre une décision: si le type de retour est le même , pourquoi ne pas autoriser le dépassement; et si ce n’est pas le cas, pourquoi ne pourrais-je pas append cette méthode? Me dire que c’est la même chose, mais ne pas permettre que cela soit annulé, est très étrange, basé sur le bon sens.

JLS # 8.4.2. Signature de la méthode

La signature d’une méthode m1 est une sous-signature de la signature d’une méthode m2 si:

  • m2 a la même signature que m1, ou

  • la signature de m1 est la même que l’effacement (§4.6) de la signature de m2.

Conformément à la règle ci-dessus, comme vos parents n’ont pas d’effacement et que votre enfant en a un, il ne s’agit donc pas d’une annulation valide.

JLS # 8.4.8.3. Exigences en matière de dérogation et de dissimulation

Exemple 8.4.8.3-4. L’effacement affecte la déviation

Une classe ne peut pas avoir deux méthodes membres avec le même nom et le même type effacer:

 class C { T id (T x) {...} } class D extends C { Object id(Object x) {...} } 

Ceci est illégal puisque D.id (Object) est membre de D, C.id (Ssortingng) est déclaré dans un supertype de D et:

  • Les deux méthodes ont le même nom, id
  • C.id (Ssortingng) est accessible à D
  • La signature de D.id (Object) n’est pas une sous-signature de celle de C.id (Ssortingng)
  • Les deux méthodes ont le même effacement

Deux méthodes différentes d’une classe ne peuvent pas remplacer des méthodes avec le même effacement:

  class C { T id(T x) {...} } interface I { T id(T x); } class D extends C implements I { public Ssortingng id(Ssortingng x) {...} public Integer id(Integer x) {...} } 

Ceci est également illégal, puisque D.id (Ssortingng) est un membre de D, D.id (Integer) est déclaré dans D, et:

  • Les deux méthodes ont le même nom, id
  • D.id (Integer) est accessible à D
  • Les deux méthodes ont des signatures différentes (et aucune des sous-signatures de l’autre)
  • D.id (Ssortingng) remplace C.id (Ssortingng) et D.id (Integer) remplace I.id (Integer) alors que les deux méthodes remplacées ont le même effacement

En outre, il donne l’exemple d’un cas où il est autorisé de super à enfant

La notion de sous-signature est conçue pour exprimer une relation entre deux méthodes dont les signatures ne sont pas identiques, mais dans lesquelles l’une peut remplacer l’autre. Plus précisément, il permet à une méthode dont la signature n’utilise pas de types génériques de remplacer une version généralisée de cette méthode. Ceci est important pour que les concepteurs de bibliothèques puissent librement générer des méthodes indépendamment des clients qui définissent des sous-classes ou des sous-interfaces de la bibliothèque.

Considérons l’exemple:

 class CollectionConverter { List toList(Collection c) {...} } class Overrider extends CollectionConverter { List toList(Collection c) {...} 

}

Maintenant, supposons que ce code a été écrit avant l’introduction des génériques, et que maintenant l’auteur de la classe CollectionConverter décide de générer le code, donc:

  class CollectionConverter {  List toList(Collection c) {...} } 

Overrider.toList ne remplacerait plus CollectionConverter.toList sans dérogation spéciale. Au lieu de cela, le code serait illégal. Cela inhiberait considérablement l’utilisation des génériques, car les auteurs de bibliothèques hésiteraient à migrer le code existant.

Eh bien, pour la première partie, la réponse serait que Java ne permet pas aux méthodes non génériques d’être remplacées par des méthodes génériques, même si l’effacement est le même. Cela signifie que cela ne fonctionnerait pas même si vous aviez simplement la méthode dominante comme:

  public  Object get(int i) 

Je ne sais pas pourquoi Java pose cette limitation (je l’ai réfléchi), je pense juste qu’il s’agit de cas particuliers mis en œuvre pour sous-classer les types génériques.

Votre deuxième définition traduirait essentiellement:

 public class D extends A{ @Override public Object get(int i){ //impl } public Object get(int i){ //impl } } 

ce qui est évidemment un problème.

De la section JLS 8.4.8.3 Ignorer et cacher :

Il s’agit d’une erreur de compilation si une déclaration de type T a une méthode membre m1 et qu’il existe une méthode m2 déclarée dans T ou un supertype de T telle que toutes les conditions suivantes soient vérifiées :

  1. m1 et m2 ont le même nom.

  2. m2 est accessible depuis T.

  3. La signature de m1 n’est pas une sous-signature (§8.4.2) de la signature de m2.

  4. La signature de m1 ou une méthode m1 remplace (directement ou indirectement) a le même effacement que la signature de m2 ou une méthode m2 remplace (directement ou indirectement).

1 et 2 tient.

3 tient aussi parce que (citation de la section 8.4.2 de JLS ):

La notion de sous-signature est conçue pour exprimer une relation entre deux méthodes dont les signatures ne sont pas identiques, mais dans lesquelles l’une peut remplacer l’autre. Plus précisément, il permet à une méthode dont la signature n’utilise pas de types génériques de remplacer une version générée de cette méthode.

Et vous rencontrez l’inverse: une méthode avec un type générique qui en substitue une sans le générique.

4 tient aussi parce que les signatures effacées sont les mêmes: public Object get(int i)

Imaginez l’invocation suivante.

 A a = new C(); a.get(0); 

En fait, vous appelez une méthode générique, mais vous ne transmettez aucun argument de type. Dans l’état actuel des choses, ce n’est pas vraiment un problème. Ces arguments de type disparaissent de toute façon lors de la génération du code. Pourtant, la réification n’a jamais été retirée de la table et les intendants de Java, le langage, ont essayé et continuent d’essayer de garder cette porte ouverte. Si les arguments de type étaient réifiés, votre appel ne fournirait aucune méthode à une méthode qui en nécessite une.

Le code original de @ RafaelT entraîne cette erreur de compilation …


 C.java:3: error: C is not abstract and does not override abstract method get(int) in A public class C extends A { ^ C.java:5: error: name clash: get(int) in C and get(int) in A have the same erasure, yet neither overrides the other public  T get ( int i ){ ^ where T is a type-variable: T extends Object declared in method get(int) 

La solution la plus simple et la plus simple pour obtenir la compilation de la sous-classe C de @RafaelT est …


 public abstract class A { public abstract Object get(int i); } 

 public class C extends A { @Override public T get(int i){ //impl return (T)someObj; } } 

Bien que je sois sûr que les autres personnes voulaient bien répondre à leurs questions. Néanmoins, certaines des réponses semblent avoir largement mal interprété le JLS .

La modification ci-dessus apscope uniquement à la déclaration de classe entraîne une compilation réussie. Ce seul fait signifie que les signatures originales de @ RafaelT sont en effet parfaitement acceptables en tant que sous-signaturescontrairement à ce que d’autres ont suggéré .

Je ne ferai pas la même erreur que d’autres qui semblent avoir répondu et essaie de prétendre que j’ai accumulé toute la documentation sur les génériques JLS. J’avoue que je n’ai pas trouvé avec 100% de certitude la cause fondamentale de l’échec de la compilation initiale du PO.

Mais je soupçonne que cela a quelque chose à voir avec une combinaison malheureuse de l’utilisation de Object tant que type de retour par le PO, en plus des subtilités confuses et bizarres typiques des génériques de Java.

Vous devriez lire sur l’ effacement .

Dans votre exemple:

 public class D extends A{ @Override public Object get(int i){ //impl } public  T get(int i){ //impl } } 

Le code d’octet généré par le compilateur pour votre méthode générique sera identique à votre méthode non générique; c’est-à-dire que T sera remplacé par la limite supérieure, c’est-à-dire Object . C’est pourquoi vous recevez l’avertissement DuplicateMethod dans votre IDE.

Déclarer la méthode comme T get(int i) n’a aucun sens sans déclarer T ailleurs – dans les arguments de la méthode ou dans un champ de la classe englobante. Dans ce dernier cas, il suffit de paramétrer la classe entière avec le type T

Il y a un problème conceptuel. Supposons que C#get() remplace A#get()

 A a = new C(); Object obj = a.get(); // actually calling C#get() 

mais C#get() nécessite un T – que doit être T ? Il n’y a aucun moyen de déterminer.

Vous pouvez protester que T n’est pas nécessaire en raison de l’effacement. C’est correct aujourd’hui. Cependant, l’effacement était considéré comme une solution de contournement “temporaire”. La plupart du temps, le système de typage n’assume pas l’effacement; il est en fait conçu avec soin de manière à pouvoir être rendu pleinement “réifiable”, c’est-à-dire sans effacement, dans la future version de Java sans rompre le code existant.