You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java Stream distinct()后修改元素致重复结果的问题咨询

Java Stream distinct() 修改对象后出现重复的问题解析

问题背景

测试Java Stream的distinct()方法时遇到异常场景:执行distinct()后修改流中的对象,最终结果出现重复项。需要明确两个问题:

  1. 为什么对象会在distinct()处理完成前进入后续操作?
  2. 如何在不遍历整个列表的情况下保证结果唯一性?

说明: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采用惰性求值+流水线式处理的机制:

  1. 惰性求值:所有中间操作(如distinct()、map())不会立即执行,只有当终端操作(如collect())触发时才开始处理元素。
  2. 流水线式处理:元素是逐个流经整个操作链的——一个元素先通过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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 06:06:45