Java: Zyklische generische Art Beziehung erlaubt es nicht, gegossen aus übergeordnetem Typ (javac bug)

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

  •  03-07-2019
  •  | 
  •  

Frage

Ich begegne ein ganz seltsames Verhalten der Java-Compiler.
Ich kann keinen übergeordneten Typen auf einen Subtyp wirken, wenn zyklische gattungsgemäßen Art Beziehung beteiligt ist.

JUnit-Testfall , um das Problem zu reproduzieren:

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
    }
}

Compiler Fehlerausgang:

_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

. Hinweis: Ich bin mit Netbeans neuesten Stamm, gebündelt Ant, neueste Java 6 Release
Ich versuchte Ant unter Verwendung von der Kommandozeile (Netbeans erzeugt eine build.xml Datei) aber es führt zu gleichen Fehler.

Was ist falsch?
Gibt es eine elegante Möglichkeit, das Problem zu lösen?

Das Merkwürdige ist: Netbeans keine Fehler markiert (nicht einmal Warnungen) in bestimmten Code.

EDIT:
Nein, jetzt verstehe ich, nichts !
Eclipse 3.4.1 markiert nicht weder Warnungen noch Fehler, und compiliert der Code ohne Probleme !!!
Wie kann das sein? Ich dachte, mit Ant von der Kommandozeile zusammen mit build.xml von Netbeans' vorgesehen wäre neutral.
Bin ich etwas fehlt?

EDIT 2:
Mit JDK7 Bibliothek und JDK7 Codeformat kompiliert Netbeans ohne Fehler / Warnungen!
(Ich bin mit 1.7.0-ea-b55)

EDIT 3:
Changed Titel, um anzuzeigen, dass wir mit einem Javac Fehler zu tun haben.

War es hilfreich?

Lösung

Ich behaupte nicht leicht, diese komplexen generische Typen zu verstehen, aber wenn Sie einen Code finden, der in javac kompiliert und nicht in ecj (die Eclipse-Compiler), dann einen Fehlerbericht mit den beiden Sun und Eklipse und beschreibt die Situationen Cleary (am besten, wenn man auch erwähnen, dass Sie beiden Fehlerberichte eingereicht und ihre jeweiligen URLs erwähnen, obwohl für Sun es eine Weile dauern kann, bis der Fehler öffentlich zugänglich ist).

Ich habe in der Vergangenheit getan hat, dass und habe wirklich gute Antworten, wo

  1. eine der Mannschaften herausgefunden, was der richtige Ansatz war (Compiler-Fehler, Warnung oder nichts geben)
  2. und der fehlerhafte Compiler wurde behoben

Da beide Compiler die gleiche Spezifikation implementieren, einer von ihnen per Definition ist falsch, wenn nur einer von ihnen den Code kompiliert wird.

Für das Protokoll:

Ich habe versucht, den Beispielcode mit javac (javac 1.6.0_13) und ecj (Eclipse-Java-Compiler 0.894_R34x, 3.4.2-Release) und javac klagte laut zu kompilieren und konnte keine .class Dateien erzeugen, während ecj nur über einige beschwert nicht verwendeten Variablen (Warnungen) und produziert alle erwarteten .class Dateien.

Andere Tipps

Ich habe mit Nicht-Generika für diese ende:

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

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

, was hält Sie zurück von den Typen ohne Platzhalter verwenden?

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
}

auf diese Weise liest er viel klarer, und es kompiliert und ausgeführt :) Ich denke, das wäre „eine elegante Weise, das Problem lösen“

Das Problem könnte sein, dass Sie versuchen, IAtom<?, ?> zu Atom werfen (was ein Atom<Atom, Space>). Wie zum Teufel ist das System an, dass ? wissen könnte Atom und Raum sein oder nicht?

Wenn Sie die Typen nicht wissen, in bleiben, wo die Generika gefüllt sind, Sie in der Regel nur die ganze Sache wegzulassen, wie in

ISpace spaceSupertype = new Space();

Das erzeugt einen Compiler-Warnung (nicht Fehler), aber der Code wird ausgeführt, nach wie vor (obwohl, wenn der tatsächliche Typ nicht kompatibel gegossen wird, werden Sie einen Laufzeitfehler erhalten).

Das Ganze ist nicht sinnvoll zu betrachten, though. Sie sagen, Sie starke Typisierung benötigen, können Sie dann kleben ? wo die Typen gehen. Dann sind Sie drehen sich um und versuchen, sie zu werfen.

Wenn Sie in der Lage sein, sie zu Raum und Atom zu werfen, sollten Sie wahrscheinlich nur diejenigen verwenden, um mit zu beginnen. Wenn Sie nicht, weil Sie andere Arten in diesen Variablen bleiben werden schließlich, Ihr Code wird wie alle Teufel brechen, wenn Sie den Laufzeittyp sowieso ändern, es sei denn, Sie eine Reihe von if / then-Anweisungen (wie bei der Ende des Kommentars).

Wirklich, obwohl, wenn Sie diesen seltsame Sachen zu tun sind, denke ich, dass wir bei schlechtem Code-Design suchen. Überdenken, wie Sie dies zu strukturieren. Vielleicht müssen Sie andere Klassen oder Schnittstellen, die Funktionalität erfüllen, die Sie hier haben.

Fragen Sie sich: „Muss ich die starken Typen wirklich? Was es mich nicht gemacht?“ Es ist besser, keine Felder hinzufügen, da Sie Schnittstellen verwenden. (Beachten Sie, dass, wenn die Felder nur über Methoden zugegriffen werden, dann sind Sie nur Methoden, um die öffentliche Schnittstelle hinzugefügt wird.) Wenn es Methoden hinzufügt, dann unter Verwendung der einzelnen Schnittstellen IAtom und ISpace hier ist eine schlechte Idee, da werden Sie nur der Lage sein, ohnehin test() mit diesem Subtyp zu verwenden. test() wird nicht an andere Implementierungen von ISpace / IAtom verallgemeinern. Wenn alle Implementierungen werden Sie haben hier in den gleichen Methoden setzen Sie test() brauchen aber nicht alle Implementierungen von IAtom / ISpace sie haben, erhalten Sie eine Zwischen Subschnittstelle müssen:

public interface IAtom2 extends IAtom
{
    [additional methods]
}

Dann können Sie IAtom2 statt IAtom in test() verwenden. Dann haben Sie, automatisch die Eingabe Sie brauchen, und müssen nicht die Generika. Denken Sie daran, wenn ein Satz von Klassen alles eine gemeinsame öffentliche Schnittstelle (Satz von Methoden und Feldern), der öffentliche Schnittstelle ein guter Kandidat für einen Supertyp oder Schnittstelle zu haben. Was ich beschreibe ist so etwas wie das Parallelogramm, Rechteck, Quadrat Beziehung, wo Sie versuchen, das Rechteck Teil zu überspringen.

Wenn Sie nicht neu zu gestalten gehen, die andere Idee ist, dass Sie die zyklischen Generika fallen und nur Instanz tun über instanceof Prüfung:

if (spaceSupertype instanceof Space)
{
    Space space = (Space)spaceSupertype;
    ...
}
else
...
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top