我试图了解JDK下游减少的实现。这是它:
public static <T, K, D, A, M extends Map<K, D>>
Collector<T, ?, M> groupingBy(Function<? super T, ? extends K> classifier,
Supplier<M> mapFactory,
Collector<? super T, A, D> downstream) {
Supplier<A> downstreamSupplier = downstream.supplier();
BiConsumer<A, ? super T> downstreamAccumulator = downstream.accumulator();
BiConsumer<Map<K, A>, T> accumulator = (m, t) -> {
K key = Objects.requireNonNull(classifier.apply(t), "element cannot be mapped to a null key");
A container = m.computeIfAbsent(key, k -> downstreamSupplier.get());
downstreamAccumulator.accept(container, t);
};
BinaryOperator<Map<K, A>> merger = Collectors.<K, A, Map<K, A>>mapMerger(downstream.combiner());
@SuppressWarnings("unchecked")
Supplier<Map<K, A>> mangledFactory = (Supplier<Map<K, A>>) mapFactory;
if (downstream.characteristics().contains(Collector.Characteristics.IDENTITY_FINISH)) {
return new CollectorImpl<>(mangledFactory, accumulator, merger, CH_ID);
}
else {
@SuppressWarnings("unchecked")
Function<A, A> downstreamFinisher =
(Function<A, A>) downstream.finisher(); //1, <------------- HERE
Function<Map<K, A>, M> finisher = intermediate -> {
intermediate.replaceAll((k, v) -> downstreamFinisher.apply(v));
@SuppressWarnings("unchecked")
M castResult = (M) intermediate;
return castResult;
};
return new CollectorImpl<>(mangledFactory, accumulator, merger, finisher, CH_NOID);
}
}
在//1
,downstreamFinisher
的类型为Function<A, D>
。根据类型参数声明<T, K, D, A, M extends Map<K, D>>
判断,类型D
不依赖于A
。那么我们为什么要把它投到Function<A, A>
。我认为,D
类型甚至可能不是A
的子类。
我错过了什么?
答案 0 :(得分:5)
如果下游收集器没有身份装订器且装订器功能返回的类型与中间容器类型不同,则此收集器实际上违反了Map
的通用类型安全性。
在收集操作期间,地图将保存A
类型的对象,即中间容器类型。然后,在操作结束时,groupingBy
的修整器将遍历地图并将修整器功能应用于每个值,并将其替换为最终结果。
当然,如果没有未经检查的操作,这将无法实现。有多种方法可以实现,您发布的变体将地图供应商的类型从Supplier<M>
更改为Supplier<Map<K, A>>
(第一个未经检查的操作),因此编译器接受地图将保存值类型A
而不是D
。这就是finisher
函数必须更改为Function<A,A>
(第二次未经检查的操作)的原因,因此它可以在地图的replaceAll
操作中使用,该操作似乎需要{{1}类型的对象尽管它实际上是A
。最后,必须将结果映射转换为D
(第三个未经检查的操作)以获取供应商实际提供的预期结果类型M
的对象。
正确的类型安全替代方法是使用不同的映射并通过使用转换中间映射的值的结果填充结果映射来执行整理操作。这不仅可能是昂贵的操作,还需要第二个供应商用于中间地图,因为提供的供应商仅生成适合最终结果的地图。显然,开发人员认为这是一种可接受的类型安全漏洞。
请注意,当您尝试使用强制类型安全的M
实现时,您会注意到不安全的操作:
Map
将使用此实现生成Stream.of("foo", "bar").collect(Collectors.groupingBy(String::length,
() -> Collections.checkedMap(new HashMap<>(), Integer.class, Long.class),
Collectors.counting()));
,因为它尝试将中间容器(数组)而不是ClassCastException
放入Map中。