Das alte "dynamischer Downcast" Problem

Status
Nicht offen für weitere Antworten.

Josquin

Mitglied
Hallo,

ich habe eine Klassenhierarchie von Message-Klassen mit

Code:
class Message ...
class UserLoginMessage extends Message ...

usw.

Jetzt gibt es ein generisches MessageHandler-Interface. Entsprechend:

Code:
class UserLoginMessageHandler implements MessageHandler<UserLoginMessage> ...

So weit so schön. Nun kommt dazu ein MessageManager, der Message-Objekte über eine Netzwerkschnittstelle empfängt und an die MessageHandler verteilen soll. Er besitzt eine Map mit dem Message-Class-Objekt als Key und einem MessageHandler vom entsprechenden Typ als Value. Beim Empfangen von Messages bekommt der Manager diese allerdings als Objekt vom Typ Message und müßte sie, bevor er sie verteilen kann, entsprechend irgendwie downcasten. Allerdings soll der Manager nicht jede Message-Unterklasse kennen müssen, weshalb der Downcast dynamisch erfolgen muß.

Der erste Versuch an der Stelle war
Code:
message.getClass().cast(message)
, was aber nur folgende Fehlermeldung bringt:

The method handleMessage(MessageConnection, capture-of ? extends Message) in the type MessageHandler<capture-of ? extends Message> is not applicable for the arguments (MessageConnection, capture-of ? extends Message)

Läßt sich das irgendwie hinbekommen?

Thx!

Mathias
 

Marco13

Top Contributor
Wie sieht handleMessage aus? Ideal wäre ein Kleines Beispiel, das verdeutlicht, WAS genau (mit welchen Methoden und Methodensignaturen) funktionieren sollte....
 

Josquin

Mitglied
Code:
public abstract class Message { 
    public final long id;
    public Message() { this.id = 17; }
}

public class UserLoginMessage extends Message {
    public final String user;
    public UserLoginMessage(String user) { this.user = user; }
}

public interface MessageHandler<T extends Message> {
    void handleMessage(T message);
}

public class UserLoginMessageHandler<UserLoginMessage> {
    public void handleMessage(UserLoginMessage message) { ... }
}

Wenn ich eine Message empfange, ist die vom Type Message. Um die an den Handler zu übergeben, muss ich sie nach T casten, was ich aber an der Stelle gar nicht kenne. Wenn ich die Message direkt übergeben will, also so:

Code:
public class MessageManager {
    private Map<String, MessageHandler<? extends Message>> handlers = new ...
    public void receiveMessage(Message message) {
        handlers.get(message.getClass().getName()).handleMessage(message);       
    }
}

sagt der Compiler:

The method handleMessage(MessageConnection, capture-of ? extends Message) in the type MessageHandler<capture-of ? extends Message> is not applicable for the arguments (MessageConnection, Message)

Irgendwie muss ich die generische Message, die die MessageConnection liefert, dynamisch in die exakte Subklasse casten, ohne selbige exakt zu kennen. Mit dem sinnlos erscheinenden Aufruf

Code:
public class MessageManager {
    private Map<String, MessageHandler<? extends Message>> handlers = new ...
    public void receiveMessage(Message message) {
        handlers.get(message.getClass().getName()).handleMessage(connection, message.getClass().cast(message));       
    }
}

proviziere ich die vielversprechende Fehlermeldung:

The method handleMessage(MessageConnection, capture-of ? extends Message) in the type MessageHandler<capture-of ? extends Message> is not applicable for the arguments (MessageConnection, capture-of ? extends Message)

Gibt es irgendwelche Ideen dazu?

Gruß Mathias
 
S

SlaterB

Gast
ohne den generischen Typ zu kennen kannst du keine generischen Methoden aufrufen

das muss auch gar nicht so ein kompliziertes Beispiel sein,

Code:
public class Test
{
    public static void main(String[] args)
        throws Exception
    {
        List<? extends Number> list = new ArrayList<Number>();

        Number n = null;
        list.add(n); // Error wie bei dir

        List l = list;
        l.add(n); // geht
    }

}
macht das gleiche
 

Landei

Top Contributor
Keine Ahnung ob das funzt, aber mal so als Denkanstoss:

Code:
public interface MessageHandler<T extends Message> { 
    void handleMessage(T message); 
    Class<T> messageClass();
} 

public class UserLoginMessageHandler<UserLoginMessage> { 
    public void handleMessage(UserLoginMessage message) { ... } 
    public Class<UserLoginMessage> messageClass() { return UserLoginMessage.class; }
}

public class MessageManager { 
    private Map<String, MessageHandler<? extends Message>> handlers = new ... 
    public void receiveMessage(Message message) { 
        Messagehandler handler = handlers.get(message.getClass().getName());
        handler.handleMessage(handler.messageClass().cast(message));        
    } 
}
 

EgonOlsen

Bekanntes Mitglied
Du hast lauter Unterklassen von Message, die in einem Manager auflaufen, der sich über getClass() aus der Map den entsprechenden Handler raussucht? Wieso lässt du dann das Generics-Gedödel nicht einfach weg, machst handleMessage(T message) zu handleMessage(Message message) und machst im jeweiligen Handler (sofern überhaupt erforderlich) den Cast auf die tatsächliche Klasse? Der Handler weiß doch, für welche Klasse er handlen soll, oder?
Oder habe ich da jetzt was falsch verstanden?

Edit: Oder du lässt die Messages sich selber handlen. Eine Message weiß immer, was sie ist und kennt auch ihren Handler (bzw. kann ihn kennen...in deinem Code tut sie es natürlich nicht). Der Manager muss dann nur auf den einkommenden Messages handle() aufrufen, die Message geht an ihren internen Handler und fertig. Der Manager braucht damit kein Wissen mehr über die existierenden Handler. Das MessageHandler-Interface könnte ebenfalls entfallen, die Handler selber bleiben typsicher.
 
G

Guest

Gast
Da hast Du nichts falsch verstanden, das kann man durchaus so machen, es wäre einfach nur schöner gewesen, wenn man es mit Generics hätte haben können (im Prinzip sind die ja genau für so was da) und eigentlich sieht der vorhandene Code so aus, als ob er es tun müßte. Ich gehe mal davon aus, daß die Tatsache, daß das nicht geht, weniger auf der formalen Schiene zu suchen ist, sondern inprimis mit der WTF-igen Implementierung der Java-Generics über type erasure zu tun hat.

Der zweite Vorschlag hat so was "natürlich objektorientiertes", ist aber für den Fall eine schlechte Wahl. Natürlich kann das MessageHandler-Zeug dann entfallen, dafür brauchen aber die Message-Objekte dann eine Art Schnittstelle zur Anwendung um anständig was tun zu können. Da es sich hierbei um zwischen Server und Clients ausgetauschte Messages handelt, würde das dazu führen, daß der Server Clientspezifische Interfaces kennen muß, um seine Messages bauen zu können und das muß nu gar nicht sein.

Der Post war auch mehr so als Versuchsballon gedacht für den Fall, daß ich da irgendwas übersehen habe, weil es, wie gesagt, formal erst mal so aussieht als müßte es eigentlich gehen. Die Möglichkeit, von der Verwendung der Generics abzusehen ist der logische Schritt, falls das mit den Generics wirklich nicht klappt.
 

EgonOlsen

Bekanntes Mitglied
Also wenn es mit Generics sein soll, dann ginge das hier:

Code:
class MessageManager {
  private Map<Class, MessageHandler> handlers = new HashMap<Class, MessageHandler>();
  
  public MessageManager() {
    handlers.put(UserLoginMessage.class, new UserLoginMessageHandler());
    handlers.put(UserLogoutMessage.class, new UserLogoutMessageHandler());
  }
  
  @SuppressWarnings("unchecked")
  public void receiveMessage(Message message) {
      handlers.get(message.getClass()).handleMessage(message);       
  }
}

Aber so richtig schön ist das auch nicht...
 
S

SlaterB

Gast
@EgonOlsen:
was soll daran generisch sein?

@gast
> Ich gehe mal davon aus, daß die Tatsache, daß das nicht geht, weniger auf der formalen Schiene zu suchen ist, sondern inprimis mit der WTF-igen Implementierung der Java-Generics über type erasure zu tun hat.

falls du damit sagen willst, dass es nicht an Generics allgemein liegt, sondern an der Implementierung,
so halte ich das für falsch,

es liegt daran, dass du nicht weißt, welchen Typ ein Objekt x hat,
und auch nichtmal, welchen Typ das Objekt hat, in welches du x einfügen willst,
in einer solchen Situation ist Generics einfach witzlos

wenn du
List<Integer> list = ..;
hast und
Object o = ..;
und o per instanceOf auf Integer testen kannst,
nun gut, dann kann man ja noch o auf Integer casten und list einfügen

andersrum:
wenn du ein Integer-Objekt hast und eine beliebige Liste, von der du weißt, dass sie nur Integer enthält,
dann kannst du die Liste ja auf List<Integer> casten, um den Integer einzufügen,
macht beides zu je 50% Sinn, aber eigentlich ziemlich blöde

besonders dämlich wirds dann, wenn die Liste unbekannt ist und das Objekt

List list = ..
Object o = ..;

nun könnte man tatsächlich sowohl die Liste auf List<Integer> casten als auch das Objekt auf Integer,
nur damit das einfügen generisch abläuft..,
aber was bringt das für einen Vorteil??


nein alles drei nahe dem Schwachsinn,
Generic ist für den normalen Programmablauf gedacht,
man hat List<Integer> + ein Integer-Objekt, dann kann man ganz normal das Objekt einfügen
 

Wildcard

Top Contributor
Ohne mir das jetzt alles durchgelesen zu haben, aber warum kein Visitor Pattern verwenden? Dann kann jeder Handler, oder Message, oder was auch immer die Callback Methode aufrufen und diejenige, die am konkretesten ist, trifft.
 

EgonOlsen

Bekanntes Mitglied
SlaterB hat gesagt.:
@EgonOlsen:
was soll daran generisch sein?
Nix. Aber der Rest vom Fest kann dann so bleiben, wie er ist (generisch), wenn er das denn unbedingt bleiben soll. Ich habe mir nur gespart, den ganzen restlichen Code nochmal zu posten.
 
Status
Nicht offen für weitere Antworten.

Ähnliche Java Themen

Neue Themen


Oben