Are there generics with type parameters (generics

2019-07-15 05:35发布

问题:

I need to define a generic Bus interface that can either be a CommandBus or QueryBus.

public interface Bus {

    <T> T execute(BusRequest<T> request);

}

public interface BusRequest<T> {
}

The above example works, but I'll want to replace BusRequest by another generic that will extend BusRequest, so that you could do the following:

public interface CommandBus extends Bus<Command> {

    // Inherited method would turn into something like
    // <T> T execute(Command<T> cmd);

}

public interface Command<T> extends BusRequest<T> {
}

How would I define the Bus interface to do that? Is it possible in Java?

I have tried:

public interface Bus<R extends BusRequest> {

    <T> T execute(R<T> request);

}

However it says:

Type 'R' does not have type parameters

回答1:

You can't express this type relationship in a way the Java compiler will understand. But you could get a similar effect by making BusRequest types know about the type of Bus they're supposed to be used with, and by making Bus know what type of Bus it is.

For example:

// B is the type of the Bus subclass
public interface Bus<B extends Bus<B>> {
    // R is the response type, and Q is the request type
    <R, Q extends BusRequest<R, B>> R execute(Q request);
}

// a request is parameterized by R, the response type, and B, the bus type
public interface BusRequest<R, B extends Bus<B>> {}

// A CommandBus is a Bus for CommandBusRequests
public static class CommandBus implements Bus<CommandBus> {
    @Override
    public <R, Q extends BusRequest<R, CommandBus>> R execute(Q request) {
        System.out.println("Got request of type: " + request.getClass());
        return null;
    }
}

public interface CommandBusRequest<T> extends BusRequest<T, CommandBus> {}

// StringCommandBusRequest is a BusRequest for a CommandBus that requests
// a response of type String
public static class StringCommandBusRequest
        implements CommandBusRequest<String> {}

These types all compile and type check, and the result looks like what I think you want:

public static void main(String[] args) throws Exception {
    StringCommandBusRequest request = new StringCommandBusRequest();
    Bus<CommandBus> bus = new CommandBus();
    String result = bus.execute(request);
}

Demo: https://ideone.com/U1TLXr

To be more useful, however, you'll probably need some bus-specific payload information in each request object. As this stands, each request type is tied to a bus type, but the bus isn't able to extract bus-specific data from the request object, because for example there could be more than one class that implements BusRequest<?, CommandBus>.

To solve this, we just need to introduce another(!) type parameter to keep track of the payload type. For example:

public interface Bus<P, B extends Bus<P, B>> {
    <R, Q extends BusRequest<R, P, B>> R execute(Q request);
}

public interface BusRequest<R, P, B extends Bus<P, B>> {
    P getPayload();
}

public static class CommandBus
        implements Bus<CommandBusRequestPayload, CommandBus> {
    @Override
    public <R, Q extends BusRequest<R, CommandBusRequestPayload, CommandBus>>
        R execute(Q request) {
            CommandBusRequestPayload payload = request.getPayload();
            System.out.println("Got payload: " + payload);
            return null;
    }
}

public static abstract class CommandBusRequest<T>
        implements BusRequest<T, CommandBusRequestPayload, CommandBus> {
    @Override
    public CommandBusRequestPayload getPayload() {
        return new CommandBusRequestPayload();
    }
}

public static class CommandBusRequestPayload {}

public static class StringCommandBusRequest
        extends CommandBusRequest<String> {}

Demo with payload: https://ideone.com/2aUwMW



回答2:

A Kind?

No. You'll need Scala (or Haskell) for that.