8 Stimmen

Java Generika: Unzulässige Vorwärtsreferenz

Bei einer generischen Schnittstelle

interface Foo<A, B> { }

Ich möchte eine Implementierung schreiben, bei der A eine Unterklasse von B sein muss.

class Bar<A, B super A> implements Foo<A, B> { }
// --> Syntax error

o

class Bar<A extends B, B> implements Foo<A, B> { }
// --> illegal forward reference

Aber die einzige Lösung, die zu funktionieren scheint, ist diese:

class Bar<B, A extends B> implements Foo<A, B> { }

was ziemlich hässlich ist, weil es die Reihenfolge der generischen Parameter umkehrt.
Gibt es Lösungen oder Umgehungsmöglichkeiten für dieses Problem?

5voto

Erick Robertson Punkte 30890

Da dies in Java nicht möglich ist, versuchen Sie, sich Bar<B, A extends B> anders.

Wenn Sie eine Variable deklarieren für Bar Sie geben zuerst die übergeordnete Klasse und dann die untergeordnete Klasse an. Das ist der Weg Bar Werke. Betrachten Sie es nicht als rückwärtsgewandt, sondern als vorwärtsgewandt. Das Elternteil sollte natürlich vor dem Kind angegeben werden. Diese zusätzliche Beziehung, die Sie hinzugefügt haben, bestimmt die Parameterreihenfolge, nicht die zugrunde liegende Schnittstelle.

1voto

Keith Irwin Punkte 5629

Nachdem ich diese Frage gesehen hatte, habe ich ein wenig mit verschiedenen Techniken experimentiert, von denen ich dachte, sie könnten funktionieren. Zum Beispiel, eine generische Schnittstelle zu bauen ISuper<B,A extends B> und dann mit Bar<A,B> implements ISuper<B,A> (und eine ähnliche Technik mit einer Unterklasse und extends anstelle von implements), aber das führt nur zu einem Typfehler, Bar.java:1: type parameter A is not within its bound . Ebenso habe ich versucht, eine Methode zu erstellen private <A extends B> Bar<A,B> foo() { return this; }; und ruft es vom Konstruktor aus auf, aber das führt nur zu der Fehlermeldung fun type Bar.java:2: incompatible types found : Bar<A,B> required: Bar<A,B>

Ich denke also, dass die Antwort leider nein lautet. Natürlich ist es nicht die Antwort, die Sie sich erhofft haben, aber es scheint, dass die richtige Antwort lautet, dass dies einfach nicht möglich ist.

1voto

Cephalopod Punkte 13896

Es wurde bereits darauf hingewiesen, dass es weder eine Lösung noch eine schöne Umgehung gibt. Hier ist, was ich schließlich getan habe. Es funktioniert nur in meinem speziellen Fall, aber Sie können sich davon inspirieren lassen, wenn Sie auf ähnliche Probleme stoßen. (Es erklärt auch, warum ich auf dieses Problem gestoßen bin)

Zunächst einmal gibt es diese Klasse (die nur die relevante Schnittstelle zeigt):

class Pipe<Input, Output> {

    boolean hasNext();

    Input getNext();

    void setNext(Output o);

}

En Foo Schnittstelle ist eigentlich

interface Processor<Input, Output> {

    process(Pipe<Input, Output> p);

}

und die Klasse Bar sollte folgendermaßen funktionieren

class JustCopyIt<Input, Output> implements Processor<Input, Output> {

    process(Pipe<Input, Output> p) {
       while (p.hasNext()) p.setNext(p.getNext());
    }

}

Am einfachsten wäre es, die Werte wie folgt zu kodieren: p.setNext((Output) p.getNext()) . Dies ist jedoch schlecht, da es erlauben würde, eine Instanz von JustCopyIt<Integer, String> . Der Aufruf dieses Objekts würde auf mysteriöse Weise an einem bestimmten Punkt fehlschlagen, aber nicht an dem Punkt, an dem der eigentliche Fehler gemacht wird.

Tun class JustCopyIt<Type> implements Processor<Type, Type> würde hier auch nicht funktionieren, weil ich dann nicht in der Lage bin, eine Pipe<String, Object> .

Also habe ich die Schnittstelle in diese geändert:

interface Processor<Input, Output> {

    process(Pipe<? extends Input, ? super Output> p);

}

Auf diese Weise kann ein JustCopyIt<List> ist in der Lage, eine Pipe<ArrayList, Collection> .

Auch wenn dies technisch gesehen die einzig gültige Lösung zu sein scheint, ist sie dennoch schlecht, weil sie 1) nur für diesen speziellen Fall funktioniert, 2) eine Änderung der Schnittstelle erfordert (was nicht immer möglich ist) und 3) den Code der anderen Prozessoren hässlich macht.

編集する。
Die Lektüre von Keiths Antwort hat mich erneut zu einer anderen Lösung inspiriert:

public abstract class Bar<A, B> implements Foo<A, B> {

    public static <B, A extends B> Bar<A, B> newInstance() {
        return new BarImpl<B, A>();
    }

    private static class BarImpl<B, A extends B> extends Bar<A, B> {
        // code goes here
    }

}

// clean code without visible reversed parameters
Bar<Integer, Object> bar1 = Bar.newInstance();
Bar<Object, Integer> bar2 = Bar.newInstance(); // <- compile error

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X