2009-08-18 7 views
16

Java peut souvent inférer des génériques en fonction des arguments (et même sur le type de retour, contrairement à C# par exemple).Génériques génériques déduits en retour type

Exemple: J'ai une classe générique Pair<T1, T2> qui vient stocke une paire de valeurs et peut être utilisé de la manière suivante:

Pair<String, String> pair = Pair.of("Hello", "World"); 

La méthode of ressemble à ceci:

public static <T1, T2> Pair<T1, T2> of(T1 first, T2 second) { 
    return new Pair<T1, T2>(first, second); 
} 

Très agréable. Toutefois, cela ne fonctionne plus pour le cas d'utilisation suivante, ce qui nécessite des caractères génériques: (. Remarquez le casting explicite de faire List.class le type correct)

Pair<Class<?>, String> pair = Pair.of((Class<?>) List.class, "hello"); 

Le code échoue avec l'erreur suivante (fournie par Eclipse):

incompatibilité de type: ne peut pas convertir TestClass.Pair<Class<capture#1-of ?>,String> à TestClass.Pair<Class<?>,String>

Cependant, appelant explicitement le constructeur fonctionne toujours comme prévu:

Pair<Class<?>, String> pair = 
    new Pair<Class<?>, String>((Class<?>) List.class, "hello"); 

Quelqu'un peut-il expliquer ce comportement? Est-ce par conception? Est-ce voulu? Est-ce que je fais quelque chose de mal ou est-ce que j'ai trébuché sur une faille dans le design/bug dans le compilateur?

conjecture sauvage: la « capture # 1 de? » Semble en quelque sorte à laisser entendre que le caractère générique est rempli par le compilateur à la volée, ce qui rend le type un Class<List>, et échouant ainsi la conversion (Pair<Class<?>, String>-Pair<Class<List>, String>) . Est-ce correct? Y a-t-il un moyen de contourner ce problème?


Par souci d'exhaustivité, voici une version simplifiée de la classe Pair:

public final class Pair<T1, T2> { 
    public final T1 first; 
    public final T2 second; 

    public Pair(T1 first, T2 second) { 
     this.first = first; 
     this.second = second; 
    } 

    public static <T1, T2> Pair<T1, T2> of(T1 first, T2 second) { 
     return new Pair<T1, T2>(first, second); 
    } 
} 
+0

Il semble que le convertisseur voit la signature de "de" car il retourne une paire ,? étend la classe > type. Pour les classes finales, il semble assez intelligent pour réduire la partie extends, c'est pourquoi il ne se plaint pas sur la chaîne. – Zed

+0

Hmmm, intéressant. Merci de m'avoir reliée ici. – jjnguy

+0

Cela fonctionne maintenant dans java8. Le type de cible est également consulté pour inferene. – ZhongYu

Répondre

13

La raison pour laquelle le constructeur fonctionne est que vous spécifiez explicitement les paramètres de type. La méthode statique travaillera également si vous faites cela:

Pair<Class<?>, String> pair = Pair.<Class<?>, String>of(List.class, "hello"); 

Bien sûr, toute la raison pour laquelle vous avez une méthode statique en premier lieu est probablement juste pour obtenir l'inférence de type (qui ne fonctionne pas avec les constructeurs à tout).

Le problème ici (comme vous l'avez suggéré) est que le compilateur exécute capture conversion. Je crois que c'est en raison de [§15.12.2.6 of the JLS]:

  • Le type de résultat de la méthode choisie est déterminée comme suit:
    • Si la méthode invoquée est déclarée avec un type de retour de vide, alors le résultat est nul.
    • Sinon, si la conversion non vérifiée était nécessaire pour que la méthode soit applicable, alors le type de résultat est l'effacement (§4.6) de le type de retour déclaré de la méthode.
    • Dans le cas contraire, si la méthode invoquée est générique, puis pour 1Dans, laissez- Fi soit les paramètres de type formel de la méthode, que Ai soit le type réel arguments inférées pour la méthode appel, et que R le déclaré le type de retour de la méthode étant invoqué. Le type de résultat est obtenu en appliquant la conversion de capture (§ 5.1.10) à R [F1: = A1, ..., Fn: = An].
    • Sinon, le type de résultat est obtenu en appliquant la conversion de conversion (§5.1.10) au type donné dans la déclaration de méthode.

Si vous voulez vraiment l'inférence, une solution de contournement possible est de faire quelque chose comme ceci:

Pair<? extends Class<?>, String> pair = Pair.of(List.class, "hello"); 

La pair variables aura un type plus large, et cela signifie un peu plus de typage dans le nom de type de la variable, mais au moins vous n'avez plus besoin de lancer l'appel de méthode.

+0

Merci beaucoup. Je considère toujours si la solution de contournement ne rend pas le noyau encore plus confus. Pour l'instant, je vais le laisser tel quel. –