可调用和泛型的集合

2022-09-04 03:52:48

我需要在并发线程中启动一堆任务并检索其结果。

这是我的代码:

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>ObjectSomeTask1 implements Callable<Double>SomeTask1 implements Callable<Object>

但我的问题是:为什么会发生这个错误?我不明白为什么我不能这样编码。任何人都可以说清楚吗?


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

这表示存在一个类型变量,使得参数是 .也就是说,此方法签名假定列表中的所有可调用对象都具有相同的类型参数。您的列表并非如此,这就是编译器拒绝您的代码的原因。TCollection<? extends Callable<T>>

api 方法应声明如下:

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

在 Java 中设计通用 API 时,这是一个众所周知的陷阱。缺少声明站点协方差(这将允许人们声明是 的子类型)需要在每次使用泛型类型时使用通配符类型指定协方差。也就是说,API 永远不应该写入 ,而应该始终写入 。当然,这是多余的,很容易忘记,如这个例子所示。Callable<String>Callable<Object>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>>ListCallable"

正在寻找的是invokeAll()Collection<? extends Callable<T>>

所以 a 是 .这部分有效。ListCollection

但是,由于Java的泛型是不变的,所以即使TObject某些东西也不会扩展,因为该东西可以是任何东西,而a以外的任何东西都不会扩展。因此,编译器不知道要推断什么。CallableCallable<T>CallableObjectCallable<Object>

所以编译器抱怨。

请注意,即使您已经并尝试调用 ,这仍然不起作用。你会有一个扩展的东西,而正在寻找扩展的东西。并且不能被证明是,因为Java的泛型是不变的。List<Callable<? extends Integer>><Integer> invokeAll()CallableIntegerinvokeAll()Callable<Integer>Callable<? extends Integer>extend Callable<Integer>

好吧,这是一个不好的例子,因为这是最后一类。但你明白了。Integer


推荐