2009-10-09 13 views
3

J'ai encore des problèmes avec certains cas de coin dans le système générique java.enums et méthodes génériques en Java

J'ai cette méthode (je suis seulement intéressé par la signature):

interface Extractor<RETURN_TYPE> { 
    public <U extends Enum<U>> RETURN_TYPE extractEnum(final Class<U> enumType); 
} 

(pensez à une interface dont les implémentations extraits parfois un EnumSet parfois une mise en œuvre d'extraire un JComboBox etc.)

et je veux l'appeler avec une classe obtenue lors de l'exécution, donc je l'appelle simplement la façon suivante:

public static <RETURN_TYPE> RETURN_TYPE extractField(final Extractor<RETURN_TYPE> extractor, final Field field) { 
    final Class<?> type = field.getType(); 
    if (type.isEnum()) 
     return extractor.extractEnum(/* error here*/type.asSubclass(Enum.class)); 
    throw new RuntimeException("the rest of the visitor is not necessary here"); 
} 

et je reçois un message d'erreur étrange: types incompatibles trouvée: java.lang.Object nécessaire: type_retour

l'emplacement du message si juste après la braket d'ouverture de l'appel, avant le « t » de type.

si je l'appelle d'un contexte non générique, il fonctionne:

Integer extractField(final Extractor<Integer> extractor, final Field field) { 
     final Class<?> type = field.getType(); 
     if (type.isEnum()) 
      return extractor.extractEnum(type.asSubclass(Enum.class)); 
     throw new RuntimeException("the rest of the visitor is not necessary here"); 
    } 

Quelqu'un at-il une explication et une solution à ce problème s'il vous plaît?

Voici un dossier complet pour les personnes qui veulent jouer avec elle:

public class Blah { 
    interface Extractor<RETURN_TYPE> { 
     public <U extends Enum<U>> RETURN_TYPE extractEnum(final Class<U> enumType); 
    } 

    public static <RETURN_TYPE> RETURN_TYPE extractField(final Extractor<RETURN_TYPE> extractor, final Field field) { 
     final Class<?> type = field.getType(); 
     if (type.isEnum()) 
      return extractor.extractEnum(/* error here*/type.asSubclass(Enum.class)); 
     throw new RuntimeException("the rest of the visitor is not necessary here"); 
    } 

    public static Integer extractField(final Extractor<Integer> extractor, final Field field) { 
     final Class<?> type = field.getType(); 
     if (type.isEnum()) 
      return extractor.extractEnum(type.asSubclass(Enum.class)); 
     throw new RuntimeException("the rest of the visitor is not necessary here"); 
    } 
} 

merci à l'avance,

Nico

+1

Votre question me rappelle un vieux dessin animé Peanuts. Linus: "Je pense que ma langue ne fonctionne pas! J'essaie de dire brlalalala mais il sort toujours brzazazaa!" Lucy: "Je pense que toute ta tête ne fonctionne pas." –

+0

Je dois dire ... sans un exemple de jouet compilable (qui n'a pas d'abstraction dénuée de sens), il est très difficile de comprendre la question. –

+0

Jonathan> tout le point est qu'il ne compile pas. Je vais ajouter une classe complète. – nraynaud

Répondre

3

Je ne serais pas surpris si c'est un bogue dans votre compilateur, en fait. Grâce à l'utilisation sérieuse de génériques (le genre de chose que vous faites, combinant des méthodes paramétrées, des caractères génériques délimités et d'autres utilisations «avancées» des génériques) j'ai rencontré deux ou trois problèmes dans la dernière année dans javac (ennuyeux, la même unité souvent compilé bien dans l'IDE).

Dans votre cas, je suis assez sûr que c'est un bug, puisque la partie que le compilateur se plaint est que extractor.extractEnum est un Object plutôt retournaient qu'un RETURN_TYPE. Et indépendamment de quelle inférence folle cela fait avec vos arguments de la méthode enum ... sait de la signature de type que l'extracteur est un Extractor<RETURN_TYPE>, donc vous devriez toujours être capable de dire . La preuve accablante est que même si vous appelez la méthode avec un argument null (supprimant ainsi toutes les complications potentielles des génériques enum dans l'argument), le compilateur se plaint encore. En particulier, il dit maintenant qu'il pense que le type de retour de l'extracteur est U<RETURN_TYPE> qui est clairement des ordures.

En général, la solution pour contourner ces problèmes consiste à lancer des distributions explicites. Le compilateur est-il satisfait si vous convertissez la sortie de extractEnum en RETURN_TYPE? Modifier: non, il est vraiment pas - il se plaint que U<RETURN_TYPE> et RETURN_TYPE sont inconvertibles - EEP ...

Si vous utilisez un compilateur 1.6 récent, je vous suggère de signaler à Sun comme cela est tout à fait un grand problème avec javac. Voici un cas de test très court qui l'exerce:

public class Test { 
    interface Sub<O> { 
    public <I extends Enum<I>> O method(final Class<I> enumType); 
    } 

    public static <O> O go(final Sub<O> sub) { 
    return sub.method(null); 
    } 
} 

P.S. C'est une convention générale d'utiliser une seule lettre majuscule pour désigner les paramètres de type générique. Je ne vais pas dire "j'ai raison, vous avez tort", mais gardez à l'esprit que j'ai trouvé votre code beaucoup plus difficile à lire et à suivre que si vous aviez utilisé Extractor à la place. (Et à en juger par la formulation de Hemal de sa réponse, c'est la même chose pour lui aussi.)

+0

Ok, merci pour votre analyse approfondie. Mais j'ai une pomme, donc je suppose qu'ils vont me jeter loin. J'achète votre explication sans vérifier, c'est logique. J'ai ajouté le nom de type long variable pour des raisons de clarté parce que les gens ne comprennent pas le sens de cette variable, dans mon code j'utilise aussi une lettre, même si je ne suis pas sûr que cette convention est vraiment bonne. – nraynaud

2

Je n'ai pas été en mesure de déduire le problème d'origine.

Ai-je raison que Extractor.extract a deux paramètres de type, U qui doit être un Enum et T qui est un type arbitraire? Dans l'appel générique, VV est à la fois T et U? Si U est VV, le paramètre doit être Class<VV>, et non Class<Enum>. Ce qui suit compile pour moi, mais comme vous pouvez voir la méthode générique doit être fournit par exemple des Class<VV>

class Outer { 
    static class Extractor<T> { 
    public <U extends Enum<U>> T extract(final Class<U> lala) { 
     return null; 
    } 
    // two type parameters, T and U 
    // U must be an enum 
    // T is arbitrary class 
    } 

    static <VV extends Enum<VV>> VV extract(final Extractor<VV> extractor, Class<VV> vvClass) { 
    final Class<?> type = null; 
    return extractor.extract(vvClass); 
    // Outer.extract returns VV 
    // T -> VV 
    // it seems VV is also U 
    } 
} 
+0

Non, désolé d'avoir indiqué mon problème d'une manière difficile à comprendre, je ne trouve pas encore de simplification. donc, T est le type de retour de l'extracteur, et U est le type enum statique. Dans mon point d'utilisation, je crée une méthode qui est toujours générique sur le type de données extrait, mais qui obtient le type enum à déclarer à partir de Field.getType(). – nraynaud

1

On dirait que votre Field.getType() va retourner une classe générique. Parce que vous allez essayer de mettre un type avec des informations de type déjà effacées, cette fonction devra émettre un avertissement "unchecked", et toutes les informations de type sur l'interface générique seront effacées.

Rappelez-vous que, avec l'effacement de type interface ressemble à ceci:

interface Extractor { 
    public Object extractEnum(final Class enumType); 
} 

Ainsi, parce que toutes les informations de type est effacé, le type de retour de extractEnum est java.lang.Object, vous devez ajouter un casting spécifique. Et c'est précisément le message d'erreur que vous avez.

Voici l'exemple modifié de votre code.

@SuppressWarnings("unchecked") 
public static <RETURN_TYPE> RETURN_TYPE extractField(
     final Extractor<RETURN_TYPE> extractor, 
     final Field field 
    ) 
{ 
    final Class type = field.getType(); // unchecked 

    if (type.isEnum()) 
    { 
     // needs cast 
     return (RETURN_TYPE) extractor.extractEnum(type); // unchecked 
    } 
    throw new RuntimeException("the rest of the visitor is not necessary here"); 
} 

MECONNAISSANCE CE COMMENTAIRE: Pour answher la question initiale sur la raison pour laquelle vous avez une erreur de compilation. Ceci est une cheville carrée dans un trou rond genre de problème. type.asSubclass(Enum.class) renvoie Class< ? extends Enum >, ce n'est toujours pas la même chose que Class<U> que l'appel d'interface attend.

+0

oui, je vais devoir faire face à un avertissement dans cette classe de toute façon. Mais cela ne me donne pas une explication de pourquoi j'ai cette erreur. – nraynaud

+0

BTW, dans mon environnement (Eclipse, JDK 1.6.0) la deuxième méthode avec le type Integer explicite n'a pas compilé non plus. Le message d'erreur spécifique peut être différent sur votre système, mais le problème sous-jacent est le même que celui décrit dans le dernier paragraphe. –

+0

Je peux concentrer tout l'avertissement en une seule déclaration, merci pour l'idée: (RETURN_TYPE) extractor.extractEnum ((Type de classe)) Mais je ne pense pas que votre idée de piquet carré est juste. regarde ça: http://stackoverflow.com/questions/1458779/how-to-cast-to-crtp-in-java et ça marche, je l'utilise 2-3 fois dans mon code. Avez-vous ces avertissements soulevés comme des erreurs? – nraynaud

Questions connexes