Java: la relation de type générique cyclique ne permet pas le transtypage depuis le supertype (bogue javac)

StackOverflow https://stackoverflow.com/questions/804247

  •  03-07-2019
  •  | 
  •  

Question

Je rencontre un comportement totalement étrange du compilateur Java.
Je ne parviens pas à convertir un supertype en sous-type lorsque le type générique cyclique relation est impliquée.

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

Remarque: j'utilise la dernière ligne réseau de Netbeans, le package Ant et la dernière version de Java 6 fournis.
J'ai essayé d'utiliser Ant depuis 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 un moyen élégant 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é.

MODIFIER:
Non, je ne comprends plus rien !
Eclipse 3.4.1 ne marque pas les avertissements ni les erreurs, et compile le code sans problème !!!
Comment se peut-il? Je pensais, en utilisant Ant de la ligne de commande avec build.xml fourni par Netbeans 'serait neutre.
Est-ce que je manque quelque chose?

EDIT 2:
À l'aide de la bibliothèque JDK7 et du format de code JDK7, Netbeans se compile sans erreurs / avertissements!
(J'utilise 1.7.0-ea-b55)

EDIT 3:
Titre modifié pour indiquer qu'il s'agit d'un bogue javac.

Était-ce utile?

La solution

Je ne prétends pas comprendre facilement ces types génériques complexes, mais si vous trouvez du code compilé dans javac et non dans ecj (le compilateur eclipse), créez un rapport de bogue avec < a href = "http://bugs.sun.com/" rel = "nofollow noreferrer"> Sun et Eclipse et décrivez clairement les situations (mieux si vous mentionnez également que vous avez rédigé les deux rapports de bogues et que vous mentionnez leurs URL respectives, bien que cela puisse prendre un certain temps avant que le bogue ne soit accessible au public).

Je l'ai fait par le passé et j'ai obtenu de très bonnes réponses là où

  1. une des équipes a déterminé quelle était la bonne approche (donner une erreur de compilation, un avertissement ou rien)
  2. et le compilateur défectueux a été corrigé

Etant donné que les deux compilateurs implémentent la même spécification, l’un d’eux est par définition incorrect, si un seul d’entre eux compile le code.

Pour mémoire:

J'ai essayé de compiler l'exemple de code avec .class (javac 1.6.0_13) et <=> (compilateur Eclipse Java 0.894_R34x, version 3.4.2) et <=> s'est plaint à haute voix et n'a pas réussi à produire de <=> fichiers, alors que <=> ne se plaint que de certaines variables inutilisées (avertissements) et génère tous les <=> fichiers attendus.

Autres conseils

J'ai fini par utiliser des produits 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
    }

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
}

ainsi, il lit beaucoup plus clair, plus il compile et fonctionne :) Je pense que ce serait & "; une manière élégante de résoudre le problème &";

.

Le problème peut être que vous essayez de convertir IAtom<?, ?> en Atom (qui est un Atom<Atom, Space>). Comment diable le système est-il supposé savoir que ? peut être Atom and Space ou non?

Lorsque vous ne connaissez pas les types à utiliser pour remplir les génériques, vous oubliez généralement tout, comme dans

ISpace spaceSupertype = new Space();

Cela génère un avertissement du compilateur (pas une erreur), mais votre code sera toujours exécuté (cependant, si le type réel n'est pas compatible avec la distribution, vous obtiendrez une erreur d'exécution).

Cependant, tout cela n’a aucun sens. Vous dites que vous avez besoin de dactylographie forte, puis vous collez IAtom où vont les types. Ensuite, vous vous retournez et essayez de les lancer.

Si vous devez pouvoir les convertir en Space et Atom, vous devriez probablement les utiliser pour commencer. Si vous ne le pouvez pas parce que vous allez éventuellement insérer d'autres types dans ces variables, votre code va casser comme d'habitude quand vous changerez le type d'exécution de toute façon, à moins que vous n'utilisiez un tas d'instructions if / then (comme à la fin de ce commentaire).

Vraiment, cependant, si vous faites des choses aussi étranges, je pense que nous examinons une mauvaise conception du code. Repensez comment 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?" Que me gagne-t-il? & Quot; Il vaut mieux ne pas ajouter de champs puisque vous utilisez des interfaces. (N'oubliez pas que si les champs sont uniquement accessibles via des méthodes, vous n'ajouterez que des méthodes à l'interface publique.) S'il ajoute des méthodes, l'utilisation des interfaces uniques ISpace et test() est une mauvaise idée, car vous ne pourrez utiliser que IAtom2 avec ce sous-type de toute façon. instanceof ne généralisera pas à d'autres implémentations de <=> / <=>. Si toutes les implémentations que vous allez mettre ici ont les mêmes méthodes dont vous avez besoin pour <=> mais que toutes les implémentations de <=> / <=> ne les ont pas, vous avez besoin d'une sous-interface intermédiaire:

public interface IAtom2 extends IAtom
{
    [additional methods]
}

Ensuite, vous pouvez utiliser <=> au lieu de <=> dans <=>. Ensuite, vous avez automatiquement la frappe dont vous avez besoin et vous n'avez pas besoin des génériques. N'oubliez pas que si un ensemble de classes a toutes une interface publique commune (ensemble de méthodes et de champs), cette interface publique est un bon candidat pour un super-type ou une interface. Ce que je décris est quelque chose comme le parallélogramme, le rectangle, la relation carrée, où vous essayez de sauter la partie rectangle.

Si vous n'allez pas refondre, l'autre idée est de supprimer les génériques cycliques et de simplement tester les instances via <=>:

if (spaceSupertype instanceof Space)
{
    Space space = (Space)spaceSupertype;
    ...
}
else
...
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top