线上产生了一个奇怪的问题,在一个求平均值的地方,却返回了不同的很奇怪的数据,排查问题花费了近大半天的时间,着实让人头大。
背景
在程序中,使用了Java8的stream流对数据进行处理。
定位
呈上犯罪现场(脱敏代码),供各位大佬嘲讽。
list.stream()
.map(Entity::getAttributeList)
.flatMap(Collection::stream).distinct()
.collect(Collectors.groupingBy(Attribute::getId))
.forEach((k, v) -> {
//业务逻辑代码
});
先上结论,问题产生的原因是distinct()
方法,造成参与计算的多条数据丢失,从而导致数据计算错误。
以下是问题的关键证据。
问题的产生原因,是上面脱敏代码中的Attribute对象没有重写hashCode()
和equals()
方法,导致认为必要的数据被认为是重复数据。
探究distinct()
万事出现问题,可直接看源码。
stream
接口
//java.util.stream
Stream<T> distinct();
关键类
java.util.stream.DistinctOps
static <T> ReferencePipeline<T, T> makeRef(AbstractPipeline<?, T, ?> upstream) {
return new ReferencePipeline.StatefulOp<T, T>(upstream, StreamShape.REFERENCE,
StreamOpFlag.IS_DISTINCT | StreamOpFlag.NOT_SIZED) {
<P_IN> Node<T> reduce(PipelineHelper<T> helper, Spliterator<P_IN> spliterator) {
// If the stream is SORTED then it should also be ORDERED so the following will also
// preserve the sort order
TerminalOp<T, LinkedHashSet<T>> reduceOp
= ReduceOps.<T, LinkedHashSet<T>>makeRef(LinkedHashSet::new, LinkedHashSet::add,
LinkedHashSet::addAll);
return Nodes.node(reduceOp.evaluateParallel(helper, spliterator));
}
/** 省略其他代码,感兴趣的客观请自行查看 */
};
}
可以看到去重实现是通过LinkendHashSet
和Node
两种数据结构进行去重,而众所周知,Set的储存需要实现元素实现hashCode()
和equals()
方法。
问题就是这么个,解决的方式也很简单,但是有一些思考。
思考
什么样的代码才是好代码?
这是一个没有答案的问题,也许明天的认知又超脱于今天。
代码是什么,是程序和系统的血肉,而程序/系统又是什么,是对现实世界的抽象。
抽象,是指具有普适性。
而什么是好的代码,映射到现实,类比一下什么是完美的生命体?
至少有一个特点,那就是不断进化,自我迭代。
《普罗米修斯》
以上。