开发者

Is there a solution to my problem using generics?

开发者 https://www.devze.com 2023-02-05 02:07 出处:网络
I\'m implementing some code using the java.util.concurrency framework.I will be passing a collection of callables to a class which will execute them in parallel.I\'m trying to work out the best way of

I'm implementing some code using the java.util.concurrency framework. I will be passing a collection of callables to a class which will execute them in parallel. I'm trying to work out the best way of getting a hold of each response in a type safe manner. Here's some code to help explain what I'm doing:

First I create my Callables, which are units of work to be called in parallel. For example, here the first unit of work returns a String and the second an Integer:

Callable<String> firstCallable = new Callable<String>(){
   public String call() {...}
};

Callable<Integer> secondCallable = new Callable<Integer>(){
   public Integer call() {...}
};

Now I fire them into my framework to run them in parallel and the trick is getting a handle to the appropiate response Object. Here's an implementation that works:

Map<Callable,Object> responseMap = ParallelSender.send(firstCallable, 
                                                       secondCallable);

where Object is the response of a particular Callable. Thus, you can do this:

String firstCallableResponse = (String)responseMap.get(firstCallable);
Integer secondCallableResponse = (Integer)responseMap.get(secondCallable);

So my question is, is it possible to avoid casting when fetching from the Map? This doesn't compile, but I'm thinking along these lines:

Map<Callable<T>, T> responseMap = ParallelSender.send(...);
String firstCallableResponse = responseMap.get(firstCallable);

such that the value returned is based on the typed parameter of the Callable key. My concern is that if anyone refactors the return type of the unit of work (say from Integer to BigDecimal or whatever) then the cast from Object will never be caught by automatic refactor tools and could lead to runtime issues.


CONCLUSION: Thanks to all the helpful comments and discussion below, I have taken a slightly different tact (though credited Sean Patrick Floyd with a correct to my question above). I ended up dropping the response map alltogether and populating the Callable object with the response. Here are the relevant code snippets:

public abstract class AbstractParallelCallable<V> implements Callable<V> {

   /** The response generated by the Call method of this class. */
   private V callableResponse;

   public V getResponse() {
       return callableResponse;
   }

   public void setResponse(V response) {
       callableResponse = response;
   }
}

Thus, I have an abstract implementation which wrappers the Callable object by storing the response. Next,in my parallel processing I get the response each Future created and populate the AbstractParallelCallable:

for (ParallelFutureTask<Object> future : futures) {
   try {
      future.getCallableHandle().setResponse(future.get());
   } catch(Exception e) {...}
}

where getCallableHandle returns the AbstractParallelCallabl开发者_开发知识库e object and ParallelFutureTask wrappers FutureTask by providing a reference to the Callable object. After execution, the calling code can then do this:

Integer theResult = firstCallable.getResponse();


Method-based approach

The only way you can do it is to encapsulate it in a method:

class ParallelSender{

    private final Map<Callable<?>, Object> map =
        new HashMap<Callable<?>, Object>();

    @SuppressWarnings("unchecked")
    public <T> T getResult(final Callable<T> callable){
        return (T) map.get(callable);
    }

}

Client code

Now your client code does not need to cast:

ParallelSender parallelSender = new ParallelSender();
Callable<Integer> integerCallable = new Callable<Integer>(){

    @Override
    public Integer call() throws Exception{
        return Integer.valueOf(1);
    }
};
Integer result = parallelSender.getResult(integerCallable);


Quick answer: No, you cannot store multiple types in a Map, not without them sharing the same parent type (such as Object). You also don't want to be able to do that. I'd consider rethinking your design here. Is it really needed you put your responses in a Map object? I'm assuming you retrieve the values from the Map again soon after.

I'd probably use a callback mechanism of sorts. As soon as the Callable is done, it stores the response locally. When the ParralelSender has sent all (assuming this'll return all Callable's results in one go), it iterates through each Callable and tells the registered listener to process the result.

Not sure if I'm making sense there, btw. I'd try to come up with an example, but lazy.


IF all your responses are of one type, then there is a solution.

Map<Callable<String>,String> responseMap = ParallelSender.send(firstCallable, 
                                                   secondCallable);
String firstCallableResponse = responseMap.get(firstCallable);

But if you're putting different result types in the same map and your code depends on their actual types, there's no way generics will help you.


Something like this:

Callable<String> firstCallable = new Callable<String>(){
   public String call() {...}
};

Callable<Integer> secondCallable = new Callable<Integer>(){
   public Integer call() {...}
};


class ParallelSender {
    ParallelResults send(Callable<Object> o,...) {...}
}

class Parallelresults {
    T <T> get(Callable<T> key) { ... }
}

ParallelResults res = ParallelSender.send(firstCallable, 
                                          secondCallable);

String s = res.get(firstCallable);
Integer i = res.get(secondCallable);

You might need to do a cast within the 'get' method.

0

精彩评论

暂无评论...
验证码 换一张
取 消