Inférence de parameters de type générique dans le chaînage de méthodes

Après avoir lu cette question , j’ai commencé à réfléchir aux méthodes génériques dans Java 8 . Plus précisément, que se passe-t-il avec les parameters de type générique lorsque les méthodes sont chaînées?

Pour cette question, je vais utiliser certaines méthodes génériques de ImmutableMap de Guava, mais ma question est plus générale et peut être appliquée à toutes les méthodes génériques chaînées.

Considérez la méthode générique ImmutableMap.of , qui a cette signature:

 public static  ImmutableMap of(K k1, V v1) 

Si nous utilisons cette méthode générique pour déclarer une Map , le compilateur en déduit correctement les types génériques:

 Map map = ImmutableMap.of("a", "b"); 

Je suis conscient que, à partir de Java 8, le mécanisme d’inférence du compilateur a été amélioré, c’est-à-dire qu’il déduit les types génériques de méthodes à partir du contexte, qui dans ce cas est une affectation.

Le contexte peut aussi être un appel de méthode:

 void someMethod(Map map) { // do something with map } someMethod(ImmutableMap.of("a", "b")); 

Dans ce cas, les types génériques de ImmutableMap.of sont déduits des types génériques de l’argument de someMethod , c’est-à-dire. Map map .

Mais lorsque ImmutableMap.builder() utiliser ImmutableMap.builder() et des méthodes en chaîne pour construire ma carte, une erreur de compilation se produit:

 Map map = ImmutableMap.builder() .put("a", "b") .build(); // error here: does not comstack 

L’erreur est:

 Error:(...) java: incompatible types: ImmutableMap cannot be converted to Map 

(Par souci de clarté, j’ai supprimé les noms de paquets du message d’erreur).

Je comprends l’erreur et pourquoi cela se produit. La première méthode de la chaîne est ImmutableMap.builder() et le compilateur n’a pas de contexte pour déduire les parameters de type. Il est donc replacé sur . Ensuite, la méthode ImmutableMap.Builder.put est appelée avec les arguments "a" et "b" puis la méthode ImmutableMap.Builder.build() est appelée, ce qui renvoie un ImmutableMap . C’est pourquoi je reçois l’erreur de types incompatibles: lorsque je tente d’affecter cette instance ImmutableMap à ma variable de Map map , le compilateur se plaint.

Je sais même comment résoudre cette erreur: je pouvais soit diviser la chaîne de méthodes en deux lignes, de sorte que le compilateur puisse maintenant déduire les parameters de type générique:

 ImmutableMap.Builder builder = ImmutableMap.builder(); Map map = builder.put("a", "b").build(); 

Ou je pourrais explicitement fournir les parameters de type génériques:

 Map map = ImmutableMap.builder() .put("a", "b") .build(); 

Ma question n’est donc pas de savoir comment résoudre ce problème, mais pourquoi j’ai besoin de fournir explicitement les parameters de type générique lors du chaînage de méthodes génériques. Ou, en d’autres termes, pourquoi le compilateur ne peut-il pas déduire les parameters de type générique dans une chaîne de méthodes, en particulier lorsque la chaîne de méthodes se situe du côté droit d’une affectation? Si cela était possible, cela casserait-il autre chose (je veux dire, lié au système de type générique)?

EDIT :

Il y a une question qui demande la même chose , mais la seule réponse disponible n’explique pas clairement pourquoi le compilateur ne déduit pas les parameters de type générique dans une chaîne de méthodes. Tout ce qu’il a, c’est une référence à un petit paragraphe de la version finale d’évaluation du langage de programmation JavaTM JSR-000335 pour évaluation (partie spécifique D):

Il y a eu un certain intérêt à autoriser l’inférence à “chaîner”: dans a (). B (), passer des informations de type de l’appel de b à l’invocation de a. Cela ajoute une autre dimension à la complexité de l’algorithme d’inférence, car des informations partielles doivent passer dans les deux sens; cela ne fonctionne que lorsque l’effacement du type de retour de a () est fixé pour toutes les instanciations (par exemple, List). Cette fonctionnalité ne s’intégrerait pas très bien dans le modèle d’expression poly, car le type de cible ne peut pas être dérivé facilement. mais peut-être qu’avec des améliorations supplémentaires, il pourrait être ajouté à l’avenir.

Je pense donc pouvoir reformuler ma question initiale comme suit:

  • Quelles seraient ces améliorations supplémentaires?
  • Comment se fait-il que le fait de passer des informations partielles dans les deux sens rende l’algorithme d’inférence plus complexe?
  • Pourquoi cela ne fonctionnerait-il que lorsque l’effacement du type de retour de a () est fixé pour toutes les instanciations (par exemple, List)? En fait, que signifie le fait que l’effacement du type de résultat d’une méthode est fixé pour toutes les instanciations?
  • Pourquoi cette fonctionnalité ne s’intègre-t-elle pas très bien dans le modèle d’expression poly? En fait, quel est le modèle d’expression poly? Et pourquoi le type de cible ne pourrait-il pas être facilement dérivé dans ce cas?

Si cela doit vraiment être un commentaire, faites-le moi savoir et je le séparerai en commentaires séparés (il ne rentrera probablement pas dans un seul commentaire).

La première poly expression est une poly expression qui peut avoir différents types dans différents contextes. Vous déclarez quelque chose dans le contextA il a typeA ; un dans le contextB et il a le typeB .

Ce que vous faites avec la création de votre carte via ImmutableMap.of("a", "b") ou ImmutableMap.of(1,2) est une telle chose. Pour être plus précis, le chapitre 15.2 du JLS indique qu’il s’agit en fait d’une expression poly Expression de création d’instance de classe .

Jusqu’ici, nous avons établi que la A generic class instance creation is a poly expression . Donc, cette création d’instance pourrait avoir différents types en fonction du contexte dans lequel elle est utilisée (et cela se produit évidemment).

Maintenant, dans votre exemple avec un ImmutableMap.builder choses ne sont pas si difficiles à déduire (si vous pouviez chaîner un builder et par exemple) Builder est déclaré comme ceci:

  public static  Builder builder() { return new Builder(); } 

et ImmutableMap.of comme ceci:

  public static  ImmutableMap of() { return ImmutableBiMap.of(); } 

Remarquez comment tous deux déclarent les mêmes types génériques K,V Ce serait probablement facile à passer d’une méthode à une autre. Mais considérons le cas où vos méthodes ( let's say 10 methods ) déclarent chacune une limite différente des types génériques, comme:

  of() ....  build() 

Etc. Et vous pouvez les enchaîner. Les informations sur les types doivent être transmises de left to right et de right to left pour que l’inférence fonctionne et, autant que je sache, il serait très difficile à faire (en plus d’être très complexe).

En ce moment, la façon dont cela fonctionne est que chaque expression poly est compilée une à la fois d’après ce que je vois (et vos exemples semblent le prouver).