import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; // 订单实体类 class Order { private Long orderId; private String status; // 状态:未完成/已完成 // 构造器、getter、setter public Order(Long orderId, String status) { this.orderId = orderId; this.status = status; } public Long getOrderId() { return orderId; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } @Override public String toString() { return "Order{orderId=" + orderId + ", status='" + status + "'}"; } } public class StreamModifyPitfall { public static void main(String[] args) { // 1. 初始化原始订单列表:2个未完成,1个已完成 List<Order> originalOrderList = new ArrayList<>(); originalOrderList.add(new Order(1L, "未完成")); originalOrderList.add(new Order(2L, "已完成")); originalOrderList.add(new Order(3L, "未完成")); System.out.println("修改前原始列表:"); originalOrderList.forEach(System.out::println); // 输出: // Order{orderId=1, status='未完成'} // Order{orderId=2, status='已完成'} // Order{orderId=3, status='未完成'} // 2. Stream筛选:获取所有“未完成”的订单,存入新集合 List<Order> unfinishedOrders = originalOrderList.stream() .filter(order -> "未完成".equals(order.getStatus())) .collect(Collectors.toList()); // 3. 修改新集合里的订单状态为“已完成” for (Order order : unfinishedOrders) { order.setStatus("已完成"); } // 堆代码 duidaima.com // 4. 再次打印原始列表:发现未完成的订单没了! System.out.println("\n修改后原始列表:"); originalOrderList.forEach(System.out::println); // 输出: // Order{orderId=1, status='已完成'} // Order{orderId=2, status='已完成'} // Order{orderId=3, status='已完成'} } }这段代码的逻辑很简单:筛选未完成订单→修改新集合的订单状态→查看原始列表。但结果完全反直觉:明明只改了unfinishedOrders,原始的originalOrderList里的订单状态居然也被改了。如果你第一次遇到这个情况,大概率会和我一样困惑:Stream的collect不是返回一个新的ArrayList吗?新集合和原始集合没关系,怎么会互相影响?
List<Order> unfinishedOrders = originalOrderList.stream() .filter(order -> "未完成".equals(order.getStatus())) // 关键:创建新的Order对象,复制原始属性 .map(order -> new Order(order.getOrderId(), order.getStatus())) .collect(Collectors.toList()); // 再修改新集合的订单状态 for (Order order : unfinishedOrders) { order.setStatus("已完成"); } // 此时原始列表不会变 System.out.println("\n修改后原始列表:"); originalOrderList.forEach(System.out::println); // 输出: // Order{orderId=1, status='未完成'} // Order{orderId=2, status='已完成'} // Order{orderId=3, status='未完成'}这种方式的好处是简单可控,缺点是如果对象属性多,手动复制会很繁琐。
import org.springframework.beans.BeanUtils; // 深拷贝工具方法(如果有嵌套属性,需要递归处理) private static Order copyOrder(Order source) { Order target = new Order(); BeanUtils.copyProperties(source, target); // 如果Order里有User属性,需要额外拷贝User: // User newUser = copyUser(source.getUser()); // target.setUser(newUser); return target; } // Stream筛选时调用拷贝方法 List<Order> unfinishedOrders = originalOrderList.stream() .filter(order -> "未完成".equals(order.getStatus())) .map(StreamModifyPitfall::copyOrder) // 调用深拷贝方法 .collect(Collectors.toList());这里要注意:如果对象有嵌套的引用类型(比如Order→User→Address),BeanUtils的浅拷贝会导致嵌套对象还是共享引用,此时需要实现真正的深拷贝(比如递归拷贝嵌套对象,或用序列化/反序列化的方式)。
回到开头的问题:Stream筛选后修改对象,原始列表为什么会变?答案很简单——集合存的是引用,筛选只是复制引用,不是复制对象。这个“反直觉”的坑,其实是Java基础的“照妖镜”:如果能理解引用传递、对象内存模型,就能一眼看穿问题;如果理解不到位,就容易踩坑。
最后给大家两个建议:
1.写代码时多问自己:“这个集合里存的是对象还是引用?修改后会不会影响其他地方?”;