Java中Stream过滤的优化方案:如何简化消息分离逻辑
这个问题很典型,咱们从性能优化和代码简洁性两个维度来拆解优化方案:
首先,先解决当前实现里的一个性能隐患:你现在用的failedMessageIds.contains()如果是基于List的话,每次判断都是O(n)的时间复杂度,当failedIds数量较多时,会拖慢整个过滤过程。第一步建议先把这个集合转成HashSet,这样contains()操作会变成O(1),大幅提升判断效率:
Set<String> failedMessageIdSet = new HashSet<>(failedMessageIds);
接下来,解决两次遍历集合的问题——你的当前代码会把messageList遍历两次,完全可以通过一次遍历完成分组,既提升性能又简化代码。
方法1:用Stream的partitioningBy(最简洁)
Collectors.partitioningBy可以把流中的元素按条件分成两组,返回一个Map<Boolean, List<Message>>,其中true对应符合条件的元素,false对应不符合的。代码如下:
Set<String> failedMessageIdSet = new HashSet<>(failedMessageIds); Map<Boolean, List<Message>> messageMap = messageList.stream() .collect(Collectors.partitioningBy(msg -> failedMessageIdSet.contains(msg.getId()))); List<Message> failedMessages = messageMap.get(true); List<Message> successMessages = messageMap.get(false);
这个写法非常简洁,而且只遍历一次集合,性能比原来的两次Stream要好。另外partitioningBy的特性是,无论是否有符合条件的元素,Map里都会存在true和false对应的空列表,不用担心get()返回null。
方法2:手动一次遍历分组(更直观)
如果你觉得Map的形式不够直观,也可以用forEach手动完成分组,本质和传统for循环逻辑一致,但写法更简洁:
Set<String> failedMessageIdSet = new HashSet<>(failedMessageIds); List<Message> successMessages = new ArrayList<>(); List<Message> failedMessages = new ArrayList<>(); messageList.forEach(msg -> { if (failedMessageIdSet.contains(msg.getId())) { failedMessages.add(msg); } else { successMessages.add(msg); } });
关于for循环vs Stream的选择
其实上面的forEach写法和传统for循环性能几乎没有差异,而Stream的partitioningBy写法在代码简洁性上更有优势,性能也不会落后。如果你的业务逻辑只是简单的分组,Stream的写法更优雅,可读性更高;如果是复杂的嵌套逻辑,for循环可能更直观,但你的场景显然用Stream更合适。
内容的提问来源于stack exchange,提问作者mengmeng

