一个由distinct方法引起的bug及思考

线上产生了一个奇怪的问题,在一个求平均值的地方,却返回了不同的很奇怪的数据,排查问题花费了近大半天的时间,着实让人头大。

背景

一个由distinct方法引起的bug及思考_第1张图片

在程序中,使用了Java8的stream流对数据进行处理。

定位

呈上犯罪现场(脱敏代码),供各位大佬嘲讽。

list.stream()
     .map(Entity::getAttributeList)
     .flatMap(Collection::stream).distinct()
     .collect(Collectors.groupingBy(Attribute::getId))
     .forEach((k, v) -> {
          //业务逻辑代码
      });

先上结论,问题产生的原因是distinct()方法,造成参与计算的多条数据丢失,从而导致数据计算错误。

以下是问题的关键证据。
一个由distinct方法引起的bug及思考_第2张图片

问题的产生原因,是上面脱敏代码中的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., LinkedHashSet<T>>makeRef(LinkedHashSet::new, LinkedHashSet::add,
                                                                 LinkedHashSet::addAll);
                return Nodes.node(reduceOp.evaluateParallel(helper, spliterator));
            }
            /** 省略其他代码,感兴趣的客观请自行查看 */
        };
    }

可以看到去重实现是通过LinkendHashSetNode两种数据结构进行去重,而众所周知,Set的储存需要实现元素实现hashCode()equals()方法。

相关知识复习

问题就是这么个,解决的方式也很简单,但是有一些思考。

思考

什么样的代码才是好代码?

这是一个没有答案的问题,也许明天的认知又超脱于今天。
代码是什么,是程序和系统的血肉,而程序/系统又是什么,是对现实世界的抽象。
抽象,是指具有普适性。

而什么是好的代码,映射到现实,类比一下什么是完美的生命体?

至少有一个特点,那就是不断进化,自我迭代。
《普罗米修斯》

以上。

你可能感兴趣的:(经验,java,bug,stream)