为什么我不能使用 Stream#toList 在 Java 16 中收集类的接口列表?

TTT*_*TTT 21 java generics type-inference java-stream java-16

我正在流式传输实现接口的类的对象。我想将它们收集为接口的元素列表,而不是实现类。

这对于 Java 16.0.1 的Stream#toList方法似乎是不可能的。例如在下面的代码中,最后一条语句将无法编译。

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class WhyDodo {

    private interface Dodo { }

    private static class FancyDodo implements Dodo { }

    public static void main(String[] args) {
        final List<Dodo> dodos = Stream.of(new FancyDodo()).collect(Collectors.toList());
        final List<FancyDodo> fancyDodos = Stream.of(new FancyDodo()).toList();

        final List<Dodo> noFancyDodos = Stream.of(new FancyDodo()).toList();
    }
}
Run Code Online (Sandbox Code Playgroud)

我们可以显式地将每个元素从 转换FancyDodoDodo。但至少为了简洁起见,我们也可以使用.collect(Collectors.toList()).

为什么我不能使用 Stream#toList 在 Java 16 中收集类的接口列表?

如果有人有比明确投射更好的解决方案,我也很乐意听到:)

Era*_*ran 21

.collect(Collectors.toList())有效,因为签名collect是:

<R, A> R collect(Collector<? super T, A, R> collector);
Run Code Online (Sandbox Code Playgroud)

重要的部分是 ? super T

这意味着toList()收集器可以解释为Collector<Dodo,?,List<Dodo>(当您分配的结果.collect()List<Dodo>),即使你流的类型Stream<FancyDodo>

另一方面,Stream's的签名toList()是:

List<T> toList()
Run Code Online (Sandbox Code Playgroud)

所以如果你为 a 执行它Stream<FancyDodo>,你会得到 a List<FancyDodo>,它不能分配给List<Dodo>变量。

我建议你在这种情况下简单地使用stream.collect(Collectors.toList())而不是stream.toList()

  • @Naman不幸的是没有办法做到这一点。您可能认为像“&lt;U super T&gt; List&lt;U&gt; toList()”这样的东西会起作用,但可惜的是,在该类型绑定中使用“super”是非法的。 (8认同)
  • 如果作者联系这里,请跟进他们,我们可以尝试使其行为与“Collectors.toList”类似吗? (2认同)

Swe*_*per 18

因为Stream.toList被声明为返回一个List<T>

default List<T> toList() { ... }
Run Code Online (Sandbox Code Playgroud)

其中T是流的元素类型。我真的想不出另一种声明方式,toList以便它可以返回您想要的列表类型。您能做的最好的事情是接受 aList<? super T>作为参数,并向其添加流元素,但这种做法违背了流的“美学”——这一切的重点是声明性的并且几乎没有状态。

您可以重写代码toList返回所需类型列表的一种方法是T手动指定类型。现在T推断是FancyDodo由于Stream.of(new FancyDodo()),但如果您愿意,您可以强制TDodo

Stream.<Dodo>of(new FancyDodo()).toList();
Run Code Online (Sandbox Code Playgroud)

现在TDodotoList将返回一个List<Dodo>.


您能做的最好的事情是接受 aList<? super T>作为参数,并向其添加流元素

实际上,这就是Collector正在做的事情。请注意如何collect接受 aCollector<? super T, DoesntMatter, R>并返回R。逆变? super T器使您能够使用这样的toList收集器。另请注意,这R是 的通用参数collect,这意味着您可以决定collect返回什么,只要您可以提供收集? super T到的收集器R

  • @MikeFHay 您是否将通配符与通用约束混淆了?`collect` 使用通配符 `? super T`,但您建议的签名使用“super T”作为通用约束。你可以做`default List&lt;? super T&gt; toList()`,当然,但不是`default &lt;R super T&gt; List&lt;R&gt; toList()`。您尝试过自己编写这样的方法吗?我收到“意外令牌”错误。 (4认同)

And*_*eas 8

泛型类型参数解析一次只发生一个方法调用。

Stream.of(new FancyDodo())始终解析TFancyDodo,所以总是会导致Stream<FancyDodo>

toList()不解析T,它只是使用已经建立的T,所以结果总是 List<FancyDodo>,并且与List<FancyDodo>不兼容List<Dodo>。请参阅:“List<Dog>的子类List<Animal>吗?为什么 Java 泛型不是隐式多态的?

collect(Collectors.toList())有一个不同TCollectors.toList(),可以解决不同TStream。由于所需的返回类型TDodo,编译器将其解析为List<Dodo>