简体   繁体   中英

Contravariant type parameter in Java?

I'm looking at porting this C# library to Java and Android

https://github.com/AndersMalmgren/SignalR.EventAggregatorProxy/tree/master/SignalR.EventAggregatorProxy.Client.DotNet

I want to port this interface

public interface IHandle<in T> where T : class
{
    void Handle(T message);
}

It needs to handle Contravariance, so that it can handle messages like IHandle<ISomeIntefaceOrBaseClass> etc

A user of the library can add subcribers and when a message comes in I check for a handler doing

subscribers
   .OfType<IHandle<T>>();

Is this possible with Java generics, or are there a more 'Java-way' of doing this?

I assume with subscribers.OfType<IHandle<T>>(); you want to get all subscribers that have the type IHandle<T> where T is the type of the message, right?

In Java you'd have to change your code somewhat, since becaus of type erasure you can't provide a type like this.

Assuming you have a single handle per type/class, you could do something like this:

interface IHandle<T> {
   Class<T> getMessageType(); //needed to query the handle for the supported class
   void handle( T message );
}

For creating implementations of that interface, you'd have several options:

Create an implementation per class:

class SpecialMessageHandle implements IHandle<SpecialMessage> {
  public Class<SpecialMessage> getMessageType() { return SpecialMessage.class; }

  public handle( SpecialMessage message ) { ... }
}

Or pass the class as a parameter:

class Handle<T> implements IHandle<T> {
  Class<T> messageClass;

  public Handle( Class<T> msgClass ) { messageClass = msgClass; }

  public Class<T> getMessageType() { return messageClass; }

  public handle( T message ) { ... }
}

You'd then probably have a map message class to handle class and handle class to subscribers (eg a Guava Multimap):

Map<Class<?>, IHandle<?>> handles = ...;
Multimap<IHandle<?>, Subscriber> subscribers = ...;

Note that I use Class<?> here because I don't know whether there is a common superclass or interface for the classes. If you have a common superclass or interface X then you'd use Class<? extends X> Class<? extends X> instead.

Additionally note that I used IHandle<?> since in Java you can't define the map in a way that key and value need to use the same generic type while supporting different keys, ie you can't do Map<Class<T>, IHandle<T>> where each key would be a different T but only allows a value that fits the key.

Adding a handle would look like this:

handles.put( handle.getMessageType(), handle );

Querying that map with a message would then work like this:

IHandle<?> handle = handles.get( message.getClass() ); 
Collection<Subscriber> handleSubscribers = subscribers.get( handle  );

Note that if you want to get the subscribers for super classes as well (eg if SpecialMessage extends Message and you want to get those that subscribed for Message as well), then you'd need to walk up the class hierarchy.

Here's what you'd do for one level up:

handles.get( message.getClass().getSuperclass() );
for( Class<?> interface : message.getClass().getInterfaces() ) {
  handles.get( message );
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM