15

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?

user1764823
  • 425
  • 2
  • 8
  • 16

2 Answers2

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

meriton
  • 68,356
  • 14
  • 108
  • 175
  • Just wondering, would rewriting the `invokeAll()` method in the Java API (as well as other methods which might be missing the wildcard) to include the wildcard break any existing code? Because that seems like a rather egregious error... Unless there's some design issue that I'm not aware of. – awksp May 21 '14 at 11:37
  • As the new signature has a more general argument type, all implementations of the executor interface would have to be changed to compile. I am not absolutely sure whether it would break binary compatibility as well, but I suspect it would. The new signature also has a more generic return type. As a Future is a covariant interface, this doesn't require changes in the source of calling code. It might still break binary compatibility, though. – meriton May 21 '14 at 11:52
  • Is binary compatibility that important? I've been told that Java highly values source compatibility, but will break binary compatibility from time to time. Haven't been around to actually experience that, though. If the next version of Java breaks compatibility, it doesn't sound like such a bad change to make, to avoid situations like this... Although I have to say that these situations don't sound that common. – awksp May 21 '14 at 11:57
  • One should not use wildcard types as return types (see Effective Java, Item 28). But in this case we can actually exploit the natural covariance of `Future` and change the signature to ` List> invokeAll(Collection extends Callable extends T>> tasks);`. – Ben Schulz May 21 '14 at 13:44
  • That method signature would be pretty hard to implement, as given `Callable extends T> task`, `executor.submit(task)` returns a `Future extends T>`, not a `Future`. What reason does Effective Java give for not using wildcards in return types? (I don't have the book at hand) – meriton May 21 '14 at 14:37
  • 1
    @meriton One could easily write a type-sound, cast-free wrapper that turns a `Future extends T>` into a `Future`. See http://stackoverflow.com/a/22815270/774444 for an answer to your question. – Ben Schulz May 21 '14 at 15:09
  • Thanks. Granted, one could do that, but that's quite a bit of effort just so the caller doesn't have to write down a wildcard type. For a class in the Java API it might indeed be worth it. – meriton May 21 '14 at 18:29
1

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.

awksp
  • 11,764
  • 4
  • 37
  • 44