我一直在进行一些 Java Streams 操作,当然它不喜欢我的代码并且拒绝提供有用的错误消息。 (作为引用,我对 C# 和 Linq 没有任何问题,所以我从概念上理解我试图做的一切。)所以我开始深入研究将显式泛型类型添加到代码中的每个方法,这样我就可以找到问题,因为过去的经验告诉我,这是一条成功的前进道路。
环顾四周时,我遇到了一些我不明白的事情。考虑以下来自 Java 源代码的代码(稍微重新格式化):
public static <T> Collector<T, ?, List<T>> toList() {
return new Collectors.CollectorImpl<>(
(Supplier<List<T>>) ArrayList::new,
List::add,
(left, right) -> {
left.addAll(right);
return left;
},
Collectors.CH_ID
);
}
为什么 toList
方法签名需要在其返回类型中使用通配符 ?
?当我删除它时,我得到
Wrong number of type arguments: 2; required: 3
和
Incompatible types. Required: Collector<T, List<T>, > Found: CollectorImpl<java.lang.Object, List<T>, java.lang.Object>
当我将 ?
更改为 Object
时,我得到(引用上面代码中的这些行/方法):
List::add – Cannot resolve method 'add' left.addAll – Cannot resolve method 'addAll(java.lang.Object)'
当我放回通配符并检查这两个时,它们是:
List – public abstract boolean add(T e) List – public abstract boolean addAll(Collection<? extends T> c)
进一步摆弄并没有教会我更多东西。
我理解在一种情况下,例如 ? extends T
,Java 中的通配符可以转换为 C# 作为新的泛型类型参数,带有 where TWildCard : T
。但是上面的 toList
是怎么回事,返回类型只有一个通配符?
最佳答案
Collection 家 have three type parameters :
T
- the type of input elements to the reduction operation
A
- the mutable accumulation type of the reduction operation (often hidden as an implementation detail)
R
- the result type of the reduction operation
对于一些 Collection 家,比如toList
, A
的类型和 R
是一样的,因为结果本身就是用来累加的。
从toList
返回的收集器的实际类型将是 Collector<T, List<T>, List<T>>
.
(收集器的一个例子是 Collectors.joining()
是 uses a StringBuilder
。)
A
的类型参数大多数时候是通配符,因为我们通常不关心它到底是什么。它的实际类型仅供收集器内部使用,we can capture it if we need to refer to it by a name :
// Example of using a collector.
// (No reason to actually write this code, of course.)
public static <T, R> collect(Stream<T> stream,
Collector<T, ?, R> c) {
return captureAndCollect(stream, c);
}
private static <T, A, R> captureAndCollect(Stream<T> stream,
Collector<T, A, R> c) {
// Create a new A, whatever that is.
A a = c.supplier().get();
// Pass the A to the accumulator along with each element.
stream.forEach(elem -> c.accumulator().accept(a, elem));
// (We might use combiner() for e.g. parallel collection.)
// Pass the A to the finisher, which turns it in to a result.
return c.finisher().apply(a);
}
您还可以在代码中看到 toList
它指定 Collectors.CH_ID
作为其特征,它指定了身份完成。这意味着它的终结器除了返回传递给它的任何内容外什么都不做。
(我在下面的评论中引用了此部分。)
这里有几个替代设计来为累加器携带类型参数。我认为这些说明了为什么 Collector
的实际设计类(class)很好。
只需使用
Object
, 但我们最终投了很多。interface Collector<T, R> { Supplier<Object> supplier(); BiConsumer<Object, T> accumulator(); BiFunction<Object, Object, Object> combiner(); Function<Object, R> finisher(); } static <T> Collector<T, List<T>> toList() { return Collector.of( ArrayList::new, (obj, elem) -> ((List<T>) obj).add(elem), (a, b) -> { ((List<T>) a).addAll((List<T>) b); return a; }, obj -> (List<T>) obj); }
隐藏累加器作为
Collector
的实现细节, 作为Collector
本身在内部进行积累。我认为这可能是有道理的,但它不太灵活,并且组合器步骤变得更加复杂。interface Collector<T, R> { void accumulate(T elem); void combine(Collector<T, R> that); R finish(); } static <T> Collector<T, List<T>> toList() { return new Collector<T, List<T>>() { private List<T> list = new ArrayList<>(); @Override public void accumulate(T elem) { list.add(elem); } @Override public void combine(Collector<T, List<T>> that) { // We could elide calling finish() // by using instanceof and casting. list.addAll(that.finish()); } @Override public List<T> finish() { return new ArrayList<>(list); } }; }
关于java - 为什么 Java Collector.toList() 需要在其返回类型中使用通配符类型占位符?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50538364/