闽公网安备 35020302035485号
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.写代码时多问自己:“这个集合里存的是对象还是引用?修改后会不会影响其他地方?”;