4

I found myself in an odd situation with generics, and so far it doesn't seem possible. I tried isolating the situation in simple classes. What I was trying to accomplish is this: A service class that dispatches handles to handle packets of data. Each handler has a Packet type parameter as there is supposed to be a handler for each packet. The service class also has a Handler type parameter because there are a few types of handlers, so several services are needed for the different handler types. This description may not be very clear, so here is the example I made:

public abstract class Service<T extends Handler<?>> {

    //Some random logic

    //If only I could use T<E>
    protected abstract <E extends Packet> void handle(T handler, E packet);

}

public class ServiceImpl extends Service<HandlerOne<?>> {

    @Override
    protected <E extends Packet> void handle(HandlerOne<?> handler, E packet) {
        handler.handle("someString", packet); //ERROR
    }

}

public interface Handler<E extends Packet> {

}

public interface HandlerOne<E extends Packet> extends Handler<E> {

    void handle(Object someObject, E packet);

}

public class HandlerOneImpl implements HandlerOne<SomePacket> {

    @Override
    public void handle(Object someObject, SomePacket packet) {
    }

}

public interface Packet {

}

public class SomePacket implements Packet {

}

Is there a way to accomplish something like this, or any suggestions? Thanks

EDIT: Here is the modified code from manouti's answer (Note: I changed SomePacket to PacketOne for clarity). The problem I encountered is creating the insance of ServiceImpl.

public static void main(String[] args) {
    HashMap<Class<? extends Packet>, HandlerOne<? extends Packet>> handlerMap = new HashMap<>();
    handlerMap.put(PacketOne.class, new HandlerOneImpl());

    ServiceImpl<Packet, HandlerOne<?>> s = new ServiceImpl<>(handlerMap);//ERROR
}

public static abstract class Service<E extends Packet, T extends Handler<E>> {

    Map<Class<? extends Packet>, T> handlerMap;

    public Service(Map<Class<? extends Packet>, T> handlerMap) {
        this.handlerMap = handlerMap;
    }

    //Some random logic

    //If only I could use T<E>
    protected abstract void handle(T handler, E packet);

}

public static class ServiceImpl<E extends Packet, T extends HandlerOne<E>> extends Service<E, T> {

    public ServiceImpl(Map<Class<? extends Packet>, T> handlerMap) {
        super(handlerMap);
    }

    @Override
    protected void handle(T handler, E packet) {
        handler.handle("someObject", packet);
    }

}
Stripies
  • 81
  • 5
  • 2
    Well first thing that comes to my eyes : you are trying to call handle which expect two parameters giving only one. – Jean-François Savard Sep 21 '15 at 20:00
  • @Jean-FrançoisSavard Oops, yeah, my bad. That's a mistake, but the main error that I can't figure out is getting the Handler and Packet in Service to align. – Stripies Sep 21 '15 at 20:07
  • Are there multiple different types added to the HashMap and then passed to ServiceImpl? As it looks like you are trying to have the class internally handle multple types, but present a single (higher kinded) type view externally? – John McClean Sep 21 '15 at 21:00
  • @JohnMcClean Yes, that's what I'm trying to accomplish. – Stripies Sep 21 '15 at 21:12
  • Can you explain why? Because if Service actually handles multiple types, it doesn't have a single type to expose. Would removing the higher order generics make things simpler e.g. class Service> ? I think there is no single type E or T that encompasses the Packets and Handlers it holds. – John McClean Sep 21 '15 at 21:18
  • @JohnMcClean The reason for it is because I had a few classes that pretty much did exactly what Service did, but for different types, so I tried to make a class that can do it for all just to reduce redundancy. – Stripies Sep 21 '15 at 21:21
  • It's a tough one :) If it's just the construction of ServiceImpl that is a problem, maybe you could simplify the types there (e.g. does it also need to implement Service? seeing as it is kind of like a service manager now?), – John McClean Sep 21 '15 at 21:42
  • @JohnMcClean It originally didn't implement Service, but I created a Service class to combine a few classes I had, that defined the types themselves, in order to condense it. I think that may have to be the way I do it, since the way I posted about may not work. – Stripies Sep 21 '15 at 21:59

2 Answers2

3

The method handle expects an Object and an argument of type E (the type parameter). You specified only the second.

Then you would still get an error even if you introduce an Object in the first parameter due to capture conversion of the ? wildcard. So I would go with two type parameters:

public abstract class Service<E extends Packet, T extends Handler<E>> {
    // Some random logic

    protected abstract void handle(T handler, E packet);

}

public class ServiceImpl<E extends Packet, T extends HandlerOne<E>> extends Service<E, T> {
    @Override
    protected void handle(T handler, E packet) {
        handler.handle(new Object(), packet); // some object as first parameter
    }
}

The issue with the current code is that in the following statement:

protected <E extends Packet> void handle(HandlerOne<?> handler, E packet) {
    handler.handle("someString", packet); //ERROR
}

you're telling the handler, which is typed to "some" packet type, to handle a packet of type E. At runtime the actual packet type of the handler may not be E.

M A
  • 71,713
  • 13
  • 134
  • 174
  • I tried something along those lines, and it is getting close to a solution, but it introduces one other problem. The ServiceImpl class will have to handle several implementations of HandlerOne, all of which have a different Packet implementation parameter. This would make creating a new ServiceImpl not possible, I think. – Stripies Sep 21 '15 at 20:19
  • @Stripies I don't see how this would prevent it from handling different implementations of `HandlerOne`. Could you provide an example in the question? – M A Sep 21 '15 at 20:25
0

You are coming up against the limits of the Java Type system, to do this properly you would need something like Higher Kinded Types. Java doesn't support this, and the closest to an implementation I've seen are the types in the (experimental) project HighJ. HighJ uses Witness types to separate the container and value types. E.g from their wiki.

The only way I found to simulate higher order type polymorphism in Java is to use the abstraction we already have, that is the abstraction of the the type parameters. To do this we need to separate the "container type" from it's value type, and then making it a type parameter itself. So instead of

   List<String> we have something like 
   Something<List, String>.

It's probably not a good idea to do this on a production (Java) project however, because it is complex and with a steep learning curve. I'd recommend you make your design simpler somewhere.

Higher Kinded Types (generics on generics) allow greater levels of abstraction (you could say

   List<T extends Processor<E>> 

for example), and to make your design simpler you can either sacrifice abstraction (duplicate some code somewhere), or type safety (dynamic languages are just as terse as those with advanced type systems).

You could compensate for the loss in type safety with extra unit tests.

Community
  • 1
  • 1
John McClean
  • 5,225
  • 1
  • 22
  • 30