Java Stream distinct()后修改元素致重复结果的问题咨询
Java Stream distinct() 修改对象后出现重复的问题解析
问题背景
测试Java Stream的distinct()方法时遇到异常场景:执行distinct()后修改流中的对象,最终结果出现重复项。需要明确两个问题:
- 为什么对象会在
distinct()处理完成前进入后续操作? - 如何在不遍历整个列表的情况下保证结果唯一性?
说明:Dto类使用Lombok的@Data注解,自动生成基于id的equals()和hashCode()方法。
代码示例
package br.com.marcusvoltolim; import lombok.AllArgsConstructor; import lombok.Data; import reactor.core.publisher.Flux; import java.util.*; import java.util.stream.Collectors; @Data @AllArgsConstructor class Dto { private Long id; } public class Main { public static void main(String[] args) { Dto dto0 = new Dto(0L); Dto dto1 = new Dto(1L); Dto dto2 = new Dto(1L); List<Dto> list = Arrays.asList(dto0, dto1, dto2); System.out.println("Original list: " + list); System.out.println("List with only distinct: " + list.stream().distinct().collect(Collectors.toList())); List<Dto> streamList = list.stream() .distinct() .map(dto -> { if (dto.getId() == 1L) { dto.setId(3L); } return dto; }) .collect(Collectors.toList()); System.out.println("Java Stream with map after distinct: " + streamList); } }
执行结果
Original list: [Dto(id=0), Dto(id=1), Dto(id=1)] List with only distinct: [Dto(id=0), Dto(id=1)] Java Stream with map after distinct: [Dto(id=0), Dto(id=3), Dto(id=3)]
预期结果
预期得到:[Dto(id=0), Dto(id=3)]
问题解析与解决方案
一、为什么会出现重复?
Java Stream采用惰性求值+流水线式处理的机制:
- 惰性求值:所有中间操作(如
distinct()、map())不会立即执行,只有当终端操作(如collect())触发时才开始处理元素。 - 流水线式处理:元素是逐个流经整个操作链的——一个元素先通过
distinct()的判断,通过后立即进入map()处理,完成后才会处理下一个元素。
你的代码中问题的核心是:distinct()保存的是已通过元素的引用,而非元素状态的快照。当第一个id=1的对象(dto1)通过distinct()后,进入map()被修改为id=3,此时distinct()的已见集合里的该对象状态已经改变。处理第二个id=1的对象(dto2)时,distinct()拿dto2(id=1)和已见集合里的dto1(现在id=3)比较,因equals()基于id,两者不相等,所以dto2被判定为新元素,通过distinct()后也被改成id=3,最终结果出现重复。
二、如何保证结果唯一性?
核心思路是避免distinct()的已见集合中元素的状态被后续操作修改,或让distinct()基于最终状态判断,以下是两种可行方案:
方案1:调整操作顺序,先map再distinct
如果业务逻辑允许,先修改对象状态,再执行distinct(),让去重基于最终状态判断:
List<Dto> streamList = list.stream() .map(dto -> { if (dto.getId() == 1L) { // 创建新对象,避免修改原列表中的数据 return new Dto(3L); } return dto; }) .distinct() .collect(Collectors.toList());
方案2:在distinct()前创建对象快照
如果必须先去重再修改,可在distinct()前复制对象,让去重基于原始状态,后续修改复制后的对象:
List<Dto> streamList = list.stream() // 复制对象,保留原始状态用于去重判断 .map(dto -> new Dto(dto.getId())) .distinct() .map(dto -> { if (dto.getId() == 1L) { dto.setId(3L); } return dto; }) .collect(Collectors.toList());
关键注意事项
- 不要在Stream后续操作中修改已通过
distinct()的对象状态,否则会破坏去重判断逻辑。 - 处理可变对象时,优先在
distinct()前完成状态修改,或创建副本用于去重,避免状态变化干扰去重。
内容的提问来源于stack exchange,提问作者Marcus Voltolim
相关产品推荐
相关产品推荐

