user1764823
user1764823

Reputation: 435

Collection of Callable and Generics

I need to launch a bunch of tasks in concurrent threads and retrieve its results.

Here is my code:

List<Callable<? extends Object>> tasks = new ArrayList<>();

// Adding some tasks whith return different types of results:
// Callable<Double>, Callable<String>, Callable<SomeOtherType>, and so on...

List<Future<? extends Object>> results = executor.invokeAll( tasks );

But the IDE shows me the next error:

no suitable method found for invokeAll(List<Callable<? extends Object>>)

    method ExecutorService.<T#1>invokeAll(Collection<? extends Callable<T#1>>)
            is not applicable
      (cannot infer type-variable(s) T#1
        (argument mismatch; List<Callable<? extends Object>> cannot be converted
                to Collection<? extends Callable<T#1>>

    method ExecutorService.<T#2>invokeAll(Collection<? extends Callable<T#2>>,long,TimeUnit)
            is not applicable
      (cannot infer type-variable(s) T#2
        (actual and formal argument lists differ in length))

  where T#1,T#2 are type-variables:
    T#1 extends Object declared in method
            <T#1>invokeAll(Collection<? extends Callable<T#1>>)
    T#2 extends Object declared in method
            <T#2>invokeAll(Collection<? extends Callable<T#2>>,long,TimeUnit)

The method signature is:

<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)

Obviously, I can replace <? extends Object> with <Object>, and make all of my tasks return Object (e.g. replace SomeTask1 implements Callable<Double> with SomeTask1 implements Callable<Object>).

But my question is: why this error occurs? I do not understand why I can't code like this. Can anyone make it clear?

Upvotes: 15

Views: 9349

Answers (2)

meriton
meriton

Reputation: 70574

<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)

This says that there is a type variable T such that the argument is a Collection<? extends Callable<T>>. That is, this method signature assumes that all Callables in the list have the same type argument. That's not the case for your list, which is why the compiler rejects your code.

The api method should have been declared as follows:

<T> List<Future<? extends T>> invokeAll(Collection<? extends Callable<? extends T>> tasks);

This is a well known pitfall when designing generic APIs in Java. The absence of declaration site covariance (which would allow one to state that Callable<String> is a subtype of Callable<Object>) requires the specification of covariance with a wildcard type at every usage of the generic type. That is, an API should never write Callable<T>, but always Callable<? extends T>. Of course, that's redundant and easy to forget, as this example shows.

In your particular case, it's probably best to do:

List<Future<?>> futures = new ArrayList<>();
for (Callable<?> callable : tasks) {
     futures.add(executor.submit(callable));
}
for (Future<?> future : futures) {
    future.get();
}

if you need this more than once, you can put it into a utility method, keeping in mind to use the proper signature ;-)

Upvotes: 9

awksp
awksp

Reputation: 11867

Sort of expanding on @skiwi's answer:

Your list is List<Callable<? extends Object>>, which is effectively "List of Callable of something"

What invokeAll() is looking for is Collection<? extends Callable<T>>

So a List is a Collection. That part works.

However, because Java's generics are invariant, Callable of something does not extend Callable<T>, even if T is Object, as that something could be anything, and a Callable of anything other than Object will not extend Callable<Object>. So the compiler has no clue what to infer.

So the compiler complains.

Note that even if you had List<Callable<? extends Integer>> and tried to call <Integer> invokeAll(), this still wouldn't work. You would have a Callable of something that extends Integer, while invokeAll() is looking for something that extends Callable<Integer>. And Callable<? extends Integer> can't be proven to extend Callable<Integer>, since Java's generics are invariant.

Well, that was a bad example, since Integer is a final class. But you get the point.

Upvotes: 1

Related Questions