简体   繁体   中英

Avoiding methods returning Wildcard types

Here's my application design where I'm trying to avoid returning wildcard types from my method so that I can invoke the methods on the returned object. Any suggestions? Is it a poor idea to have a static utility method to find and return the right handler implementation like below after all?

The problem: I have a bunch of requests (and responses corresponding to them). These requests have to be handled and the results of the handling (the responses) may be used for further processing. Which request to be handled at a given moment depends on a few criteria (this is an enum ).

My approach: My solution was to build several handlers, one for each of the request-response type combos and having them all implement a common interface with a method to handle the request.

Here's how it goes:

A family of handler classes implementing the Handler interface:

interface Handler<Q extends Request, R extends Response> {
  R handleRequest(Q);
}

and a static utility method companion class to find the right handler matching the given criteria:

class Handlers {
  Handler<? extends Request, ? extends Response> handlerForCriteria(Criteria criteria) {
    switch (criteria):
    ...
    // since there are several handler implementations, I couldn't avoid the wildcard 
    // types for the request and response
  }
}

Here's where I need to use it:

Handler<? extends Request, ? extends Response> matchingHandler = 
    Handlers.handlerForCriteria(criteria);
MyResponse response = matchingHandler.handleRequest(myRequest);

I'd like to be able to call the handleRequest like above, but obviously I'm not allowed to since the matchingHandler contains wildcard types (it can't tell for certain if the unknown wildcard type of the request was the same type as `myRequest).

Use a typed method with a cast if necessary:

class Handlers {
    <R extends Request, T extends Response> Handler<R, T> handlerForCriteria(Criteria criteria) {
        Handler<?, ?> handler = someStrategryThatIsUntyped();
        return (Handler<R, T>)handler; // just cast
    }
}

So, why/when is the cast "safe"?

Sometimes the case is both needed and safe. For example, consider this code:

private Map<Class<?>, Handler<?>> handlers = new HashMap<>();

And you populate it in a way that guarantees that the type of the class equals the type of the handler (which can't be done in the map's declaration):

public <T> void register(Class<T> clazz, Handler<T> handler) {
    handlers.put(clazz, handler);
}

but the method that finds the handler can't be compiled safely based on the map's definition:

public <T> Handler<T> findHandler(Class<T> clazz) {
    Handler<?> handler = handlers.get(clazz);
    // Although the compiler can't assert it, the handler is of type T
    // The cast is safe, because we programmatically constrain map's entries
    return (Handler<T>)handler; // safe
}

This is what I would do:

Firstly, for most cases the below solution works if it is not required to know the precise type of only Response .

class Handlers {
    static Handler<? super Request,? extends Response> handlerForCriteria(Criteria criteria) {
        return null;
    }
}
class CustomRequest implements Request{}

Handler<? super Request, ? extends Response> handler = Handlers.handlerForCriteria(null);
Response response = handler.handleRequest(new CustomRequest() {});

I find it philosophically wrong to precisely know the return type in this case. if you expect that the return type is say CustomResponse , it is your reliance on that fact that handleForCriteria for that argument will always return CustomResponse . What if it changes in future. Extra maintenance effort.

static <E extends Request, T extends Response> Handler<E,T> handlerForCriteria(Criteria criteria) {
        return null;
    }
 Handler<CustomRequest, CustomResponse> handler = Handlers.handlerForCriteria(null);
 CustomResponse response = handler.<CustomResponse>handleRequest(new CustomRequest() {});

In case if you know it strongly then probably above might be a solution. But I would never do it. I would rather prefer manual type-casting than having a generic type parameter at method level.

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