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)
我们可以显式地将每个元素从 转换FancyDodo
为Dodo
。但至少为了简洁起见,我们也可以使用.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()
。
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())
,但如果您愿意,您可以强制T
为Dodo
:
Stream.<Dodo>of(new FancyDodo()).toList();
Run Code Online (Sandbox Code Playgroud)
现在T
是Dodo
,toList
将返回一个List<Dodo>
.
您能做的最好的事情是接受 a
List<? super T>
作为参数,并向其添加流元素
实际上,这就是Collector
正在做的事情。请注意如何collect
接受 aCollector<? super T, DoesntMatter, R>
并返回R
。逆变? super T
器使您能够使用这样的toList
收集器。另请注意,这R
是 的通用参数collect
,这意味着您可以决定collect
返回什么,只要您可以提供收集? super T
到的收集器R
。
泛型类型参数解析一次只发生一个方法调用。
Stream.of(new FancyDodo())
将始终解析T
到FancyDodo
,所以总是会导致Stream<FancyDodo>
。
toList()
不解析T
,它只是使用已经建立的T
,所以结果总是 List<FancyDodo>
,并且与List<FancyDodo>
不兼容List<Dodo>
。请参阅:“是List<Dog>
的子类List<Animal>
吗?为什么 Java 泛型不是隐式多态的? ”
collect(Collectors.toList())
有一个不同T
的Collectors.toList()
,可以解决不同T
的Stream
。由于所需的返回类型T
为Dodo
,编译器将其解析为List<Dodo>
。