Stripies
Stripies

Reputation: 81

Generic class which has another generic class as parameter

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);
    }

}

Upvotes: 4

Views: 155

Answers (2)

John McClean
John McClean

Reputation: 5313

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.

Upvotes: 0

M A
M A

Reputation: 72854

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.

Upvotes: 3

Related Questions