Сборники и универсалы
Мне нужно запустить кучу задач в параллельных потоках и получить результаты.
Вот мой код:
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 );
Но IDE показывает мне следующую ошибку:
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)
Подпись метода:
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
Очевидно, я могу заменить <? extends Object>
на <Object>
и вернуть все мои задачи Object
(например, заменить SomeTask1 implements Callable<Double>
на SomeTask1 implements Callable<Object>
).
Но мой вопрос: почему эта ошибка возникает? Я не понимаю, почему я не могу так писать. Кто-нибудь может это понять?
Ответы
Ответ 1
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
Это говорит о том, что существует переменная типа T
такая, что аргумент равен Collection<? extends Callable<T>>
. То есть, эта сигнатура метода предполагает, что все Callables в списке имеют один и тот же аргумент типа. Это не относится к вашему списку, поэтому компилятор отклоняет ваш код.
Метод api должен быть объявлен следующим образом:
<T> List<Future<? extends T>> invokeAll(Collection<? extends Callable<? extends T>> tasks);
Это хорошо известная ошибка при разработке общих API-интерфейсов в Java. Отсутствие ковариации сайта объявления (что позволило бы утверждать, что Callable<String>
является подтипом Callable<Object>
) требует спецификации ковариации с подстановочным типом при каждом использовании родового типа. То есть API никогда не должен писать Callable<T>
, но всегда Callable<? extends T>
. Конечно, это избыточно и легко забыть, как показывает этот пример.
В вашем конкретном случае это, вероятно, лучше всего сделать:
List<Future<?>> futures = new ArrayList<>();
for (Callable<?> callable : tasks) {
futures.add(executor.submit(callable));
}
for (Future<?> future : futures) {
future.get();
}
если вам это нужно более одного раза, вы можете поместить его в метод утилиты, имея в виду использовать правильную подпись; -)
Ответ 2
Отклонение от ответа на @skiwi:
Ваш список List<Callable<? extends Object>>
, который эффективно "List
of Callable
чего-то"
Что ищет invokeAll()
- это Collection<? extends Callable<T>>
Итак, a List
является Collection
. Эта часть работает.
Однако, поскольку Java-генерики являются инвариантными, Callable
чего-то делает не расширение Callable<T>
, даже если T
является Object
, поскольку это может быть что угодно, а Callable
ничего, кроме Object
, будет не растягиваться Callable<Object>
. Поэтому компилятор не имеет понятия, что делать.
Поэтому компилятор жалуется.
Обратите внимание, что даже если у вас есть List<Callable<? extends Integer>>
и попытался позвонить <Integer> invokeAll()
, это все равно не сработает. У вас будет Callable
того, что расширяет Integer
, а invokeAll()
ищет что-то, что расширяет Callable<Integer>
. И Callable<? extends Integer>
не может быть доказано extend Callable<Integer>
, так как родовые Java являются инвариантными.
Ну, это был плохой пример, так как Integer
- это последний класс. Но вы понимаете.