Ich habe es immer schwer, Generika mit Sammlungen und Platzhaltern zu verwenden.
Also hier ist die folgende Karte. Ich möchte die Sammlung von Handlern für einen bestimmten Pakettyptyp beibehalten.
%Vor% Und das PacketListener
Nun möchte ich die Listener wie folgt von der eingehenden Paketklasse abhängig machen:
%Vor%Und schließlich möchte ich einen solchen Aufruf durchführen
%Vor%Alles, was ich bekomme, sind nur eine Menge Fehler. Liegt es an Platzhaltern in der Deklaration der Sammlung? Ist es möglich, eine solche Lösung zu erreichen?
Es gibt ein schönes Bild: In einer der anderen Antworten , die Ihnen dieses Problem erklären können.
Das Ding heißt PECS, was für
stehtProducer
extends
und Consumersuper
.
TL; DR: Sie können nur beide add
und get
von / zu einer Sammlung mit einem konkreten Typ ( T
). Sie können T
(und seine möglichen Subtypen) mit T extends Something
erhalten und Sie können Something
zu einem Collection
mit T super Something
hinzufügen, aber Sie können nicht beide Wege gehen: also Ihre Fehler.
Ihr Problem beginnt hier:
%Vor% Sie erwarten (oder vielleicht nur hoffen), dass Sie die beiden ?
miteinander verbinden können, damit ein Lookup mit einem Schlüssel vom Typ Class<T>
zu einem Wert vom Typ List<PacketListener<T>>
führt. Leider gibt es keine Möglichkeit, Java mitzuteilen, dass die beiden ?
gleich sind, aber unterschiedliche (aber eingeschränkte) Typen annehmen können.
Dieses Problem wird normalerweise mit den oben erwähnten covariance/contravariance
-Methoden gelöst, aber in Ihrem Fall müssen Sie sowohl als auch aus Ihrer Sammlung lesen. Sie müssen daher ein invariance
verwenden.
Ich glaube, eine Lösung für Ihr Problem besteht darin, die beiden Objekte in eine Hilfsklasse zu binden und so die Invarianz dort einzuführen. Auf diese Weise können Sie ihre Gleichheit beibehalten und sie trotzdem unter Einschränkungen variieren lassen.
Einige davon sind ein bisschen hacky IMHO (d. h. es gibt einige Casts), aber zumindest können Sie Ihr Ziel erreichen und Sie sind immer noch Typ sicher. Die Modelle sind nachweisbar gültig.
%Vor% Beachten Sie, dass, obwohl allgemein angenommen wird, dass Sie etwas falsch machen, wenn Sie etwas während der Verwendung von Generika umsetzen müssen - in diesem Fall können wir aufgrund der Laufzeit sicher sein, dass alle Listeners<T>
Objekte in der Map werden durch ihre Class<T>
kodiert und daher ist die eingeschlossene Liste tatsächlich ein List<PacketListener<T>
.
Das Folgende ähnelt der Antwort von @OldCurmudgeon.
Der Schlüsselpunkt ist auch das Feld listeners
. Aber ich erkläre es so:
Der Punkt hier ist, dass wir die Liste als Map-Wert-Typ loswerden. DelegatingPacketListener
wird wie folgt deklariert:
Nun, da DelegatingPacketListener
nur Listener vom Typ Packet
unterstützt, benötigen wir eine spezifischere Implementierung von PacketListener
:
Bitte beachten Sie, dass der Typparameter T
in der implements -Klausel nicht verwendet wird. Es ist nur für die Implementierung verwendet. Wir werden jedes PacketListener
, das an die API übergeben wurde, in ein WrappingPacketListener
einbinden. Die Implementierung ist also so:
Die API hat sich leicht geändert für getPacketListeners
, die keinen generischen Typ mehr verwendet.
Im Vergleich mit der Lösung von OldCurmudgeon bleibt diese an der bereits vorhandenen Schnittstelle PacketListener
haften und erfordert keine ungeprüfte Umwandlung.
Beachten Sie, dass die Implementierung nicht threadsicher ist, da die Implementierung von addPacketListener
eine Synchronisation auf dem Kartenschlüssel erfordert (wie der ursprüngliche Code auch benötigt). Die Kapselung der Liste der Paketlistener in immutable DelegatingPacketListener
ist jedoch wahrscheinlich besser für den Parallelbetrieb geeignet.