2009-04-29 3 views
5

Je rencontre un comportement totalement étrange du compilateur Java.
Je ne peux pas convertir un supertype en un sous-type lorsque le type générique cyclique relation est impliqué.Java: La relation de type générique cyclique n'autorise pas la conversion de supertype (bug javac)

cas de test JUnit pour reproduire le problème:

public class _SupertypeGenericTest { 

    interface ISpace<S extends ISpace<S, A>, A extends IAtom<S, A>> { 
    } 

    interface IAtom<S extends ISpace<S, A>, A extends IAtom<S, A>> { 
    } 

    static class Space 
      implements ISpace<Space, Atom> { 
    } 

    static class Atom 
      implements IAtom<Space, Atom> { 
    } 

    public void test() { 
     ISpace<?, ?> spaceSupertype = new Space(); 
     IAtom<?, ?> atomSupertype = new Atom(); 

     Space space = (Space) spaceSupertype; // cast error 
     Atom atom = (Atom) atomSupertype; // cast error 
    } 
} 

sortie d'erreur du compilateur:

_SupertypeGenericTest.java:33: inconvertible types 
found : pinetag.data._SupertypeGenericTest.ISpace<capture#341 of ?,capture#820 of ?> 
required: pinetag.data._SupertypeGenericTest.Space 
       Space space = (Space) spaceSupertype; 
            ^

_SupertypeGenericTest.java:34: inconvertible types 
found : pinetag.data._SupertypeGenericTest.IAtom<capture#94 of ?,capture#48 of ?> 
required: pinetag.data._SupertypeGenericTest.Atom 
       Atom atom = (Atom) atomSupertype; 
           ^
2 errors 

Note: J'utilise Netbeans le dernier tronc, livré Ant, dernière version de Java 6 Libération.
J'ai essayé d'utiliser Ant à partir de la ligne de commande (Netbeans génère un fichier build.xml) mais il en résulte les mêmes erreurs.

Qu'est-ce qui ne va pas?
Existe-t-il une manière élégante de résoudre le problème?

La chose étrange est: Netbeans ne marque pas les erreurs (pas même les avertissements) dans le code donné.

EDIT:
Non, je comprends maintenant rien!
Eclipse 3.4.1 ne marque ni les avertissements ni les erreurs, et compile le code sans problème !!!
Comment cela peut-il être? Je pensais, en utilisant Ant de la ligne de commande avec build.xml fourni par Netbeans serait neutre.
Ai-je raté quelque chose?

EDIT 2:
L'utilisation bibliothèque JDK7 et le format de code JDK7, NetBeans compile sans erreurs/avertissements!
(J'utilise 1.7.0-ea-B55)

EDIT 3:
Changé titre pour indiquer que nous avons affaire à un bug de javac.

+0

Uh-oh. N'ai-je pas déjà vu ça avec ISpace et IAtom auparavant? Et je ne pouvais pas le comprendre alors non plus. –

+0

Super. Je l'ai déjà vu, et c'est devenu plus compliqué depuis. –

+0

Ouais, c'est encore moi;) Le but de cette relation de type générique cyclique est: Je veux rendre le 'Espace' capable de renvoyer des 'Atomes' fortement typés, et rendre 'Atom' capable de renvoyer un parent fortement typé ' Espace'. En outre, il y a beaucoup de sous-types envolés ... Longue histoire;) –

Répondre

2

Je ne prétends pas comprendre facilement ces types génériques complexes, mais si vous trouvez un code qui compile en javac et ne dans ecj (le compilateur eclipse), puis déposez un rapport de bogue avec Sun et Eclipse et décrivez les situations de façon claire (mieux si vous mentionnez également que vous avez déposé les deux rapports de bogue et mentionnez leurs URL respectives, bien que pour Sun il peut s'écouler un certain temps le bug est accessible au public).

Je l'ai fait dans le passé et a obtenu de très bonnes réponses où

  1. l'une des équipes cernées ce que l'approche correcte a été (donner erreur de compilation, d'avertissement ou rien)
  2. et défectueux compilateur a été fixé

Comme les deux compilateurs mettent en œuvre les mêmes spécifications, l'un d'entre eux est par définition erronée, si un seul d'entre eux compile le code.

Pour mémoire:

J'ai essayé de compiler le code exemple avec javac (javac 1.6.0_13) et ecj (Eclipse compilateur Java 0.894_R34x, 3.4.2 release) et javac se plaignirent hautement et n'a produit aucune .class, tandis que ecj se plaignait seulement de certaines variables inutilisées (avertissements) et produisait tous les fichiers .class attendus.

0

J'ai fini par utiliser les non-génériques pour cela:

@Test 
    public void test() { 
      ISpace spaceSupertype = new Space(); 
      IAtom atomSupertype = new Atom(); 

      Space space = (Space) spaceSupertype; // ok 
      Atom atom = (Atom) atomSupertype; // ok 
    } 
0

Qu'est-ce qui vous empêche d'utiliser les types sans caractères génériques?

public void test() { 
    ISpace<Space, Atom> spaceSupertype = new Space(); 
    IAtom<Space, Atom> atomSupertype = new Atom(); 

    Space space = (Space) spaceSupertype; // no error 
    Atom atom = (Atom) atomSupertype; // no error 
} 

cette façon, il lit beaucoup plus clair, plus il compile et fonctionne :) je pense que ce serait « une façon élégante de résoudre le problème »

+0

Non, c'est juste un cas d'utilisation. J'ai besoin de supertypes pour travailler avec TOUS les sous-types possibles. –

0

Le problème est peut-être que vous essayez de jeter IAtom<?, ?> à Atom (qui est un Atom<Atom, Space>). Comment diable le système est-il censé savoir que ? pourrait être Atom et Space ou pas?

Lorsque vous ne connaissez pas les types de coller dans où les génériques sont remplis, vous habituellement laisser juste à côté de la chose entière, comme dans

ISpace spaceSupertype = new Space(); 

qui génère un avertissement compilateur (pas d'erreur), mais votre code sera toujours exécuté (bien que si le type réel n'est pas compatible, vous obtiendrez une erreur d'exécution).

Tout cela n'a pas de sens à regarder, cependant. Vous dites que vous avez besoin de taper fort, alors vous collez ? où les types vont. Ensuite, vous vous retournez et essayez de les lancer.

Si vous devez être en mesure de les lancer dans Space et Atom, vous devriez probablement les utiliser pour commencer. Si vous ne pouvez pas parce que vous allez coller d'autres types dans ces variables finalement, votre code va se casser comme tout diable quand vous changez le type d'exécution de toute façon, sauf si vous utilisez un tas d'instructions if/then (comme au fin de ce commentaire). Vraiment, cependant, si vous faites des choses aussi étranges, je pense que nous sommes en train de chercher une mauvaise conception de code. Repensez à la façon dont vous structurez cela. Peut-être avez-vous besoin d'autres classes ou interfaces pour remplir les fonctionnalités que vous avez ici.

Demandez-vous: «Ai-je vraiment besoin des types forts? Il vaut mieux ne pas ajouter de champs puisque vous utilisez des interfaces. (Gardez à l'esprit que si les champs ne sont accessibles que par des méthodes, alors vous n'ajoutez que des méthodes à l'interface publique.) Si vous ajoutez des méthodes, utilisez les interfaces simples IAtom et ISpace ici, c'est une mauvaise idée. être capable d'utiliser test() avec ce sous-type de toute façon. test() ne généralisera pas à d'autres implémentations de ISpace/IAtom.Si toutes les implémentations vous mettrez ici les mêmes méthodes que vous avez besoin pour test() mais pas toutes les implémentations de IAtom/ISpace ont, vous avez besoin d'un intermédiaire subinterface:

public interface IAtom2 extends IAtom 
{ 
    [additional methods] 
} 

Ensuite, vous pouvez utiliser IAtom2 au lieu de IAtom dans test(). Ensuite, vous avez automatiquement la saisie dont vous avez besoin et n'avez pas besoin des génériques. Rappelez-vous, si un ensemble de classes ont toutes une interface publique commune (ensemble de méthodes et de champs), cette interface publique est un bon candidat pour un supertype ou une interface à avoir. Ce que je décris est quelque chose comme le parallélogramme, le rectangle, la relation carrée, où vous essayez d'ignorer la partie rectangle.

Si vous n'êtes pas aller à redessiner, l'autre idée est que vous laissez tomber les génériques cycliques et juste faire tester par exemple instanceof:

if (spaceSupertype instanceof Space) 
{ 
    Space space = (Space)spaceSupertype; 
    ... 
} 
else 
... 
Questions connexes