资讯动态

第16章 集合的不可变、只读视图与防御性封装

发布时间:2026/8/9 9:57:04 来源:尧图企业网站定制
这一章是JDK 集合工程设计的最后一章。前面学习HashMap快速查找ConcurrentHashMap并发访问TreeMap有序模型Queue任务流转PriorityQueue优先级调度但是企业级代码还有一个非常重要的问题如何控制数据的修改权避免对象被意外改变。这就是不可变集合Immutable Collection只读视图Unmodifiable View防御性拷贝Defensive Copy一、为什么需要不可变集合先看一个生产中的问题。订单对象public class Order { private ListOrderItem items; public ListOrderItem getItems(){ return items; } }调用方order.getItems().clear();结果订单里面商品没了。问题谁都有权限修改内部状态。在大型系统对象可能经过ControllerServiceDomainRepository几十层调用。如果任何地方修改集合Bug 非常难定位。所以高级设计对象拥有自己的状态修改权。二、不可变对象思想不可变Immutable意思对象创建之后状态不能改变。例如JavaString就是不可变对象。代码String s hello; s s world;不是修改原对象。而是创建新对象。集合也是一样。三、Mutable vs Immutable可变集合例如ListString list new ArrayList(); list.add(A); list.add(B);状态变化[] [A] [A,B]不可变集合例如ListString list List.of(A,B);创建后[A,B]永远不能修改。执行list.add(C);异常UnsupportedOperationException四、Java 传统不可变集合Java 8通常Collections.unmodifiableXXX()例如ListString list new ArrayList(); list.add(A); ListString readonly Collections.unmodifiableList(list);现在readonly不能修改。readonly.add(B);异常。但是注意一个重要问题:它不是不可变对象五、unmodifiableList 是只读视图什么意思看源码思想class UnmodifiableList{ private final List list; }它保存原来的 List 引用。例如ListString source new ArrayList(); source.add(A); ListString view Collections.unmodifiableList(source);关系source | | v [A] view | | v 同一个对象如果修改 sourcesource.add(B);那么view看到[A,B]所以它只是禁止自己修改。不是数据真正冻结。六、真正不可变集合Java 9提供List.of() Set.of() Map.of()例如ListString list List.of( A, B );特点创建后不能修改不依赖外部集合内部状态不可变例如ListString source new ArrayList(); source.add(A); ListString immutable List.copyOf(source);之后source 修改source.add(B);immutable仍然[A]七、不可变集合的优势1. 防止误修改例如领域对象class Customer{ private ListAddress addresses; }如果直接暴露getAddresses()风险任何地方都能修改。正确public ListAddress getAddresses(){ return List.copyOf(addresses); }调用者只能读。八、不可变集合天然线程安全为什么线程安全最大问题共享状态修改。例如线程A修改List线程B读取List需要锁。不可变没有修改。所以多个线程安全读取。例如配置MapString,String config Map.of( timeout,30 );多个线程随便读。九、防御性拷贝这是 P7 必须掌握的概念。问题对象内部状态泄露。例如class User{ private ListString roles; public User(ListString roles){ this.rolesroles; } }调用ListString roles new ArrayList(); roles.add(ADMIN); User user new User(roles);现在外部roles.clear();User内部也变了。因为引用相同。十、防御性拷贝解决构造方法public User(ListString roles){ this.roles new ArrayList(roles); }现在内存外部List [A,B] User内部List [A,B]两个对象。外部修改不会影响 User。十一、返回值也需要防御性拷贝错误public ListString getRoles(){ return roles; }调用user.getRoles().clear();内部被修改。方案1返回副本public ListString getRoles(){ return new ArrayList(roles); }方案2返回不可变public ListString getRoles(){ return List.copyOf(roles); }生产更推荐方案2。十二、DDD 中为什么大量使用不可变对象这个和你前面学习 DDD 强相关。DDDValue Object值对象例如金额Money地址Address客户编号CustomerId应该不可变。例如金额错误money.setAmount(100);风险历史状态被修改。正确Money money1 new Money(100); Money money2 money1.add(new Money(50));产生新对象。十三、不可变集合在企业系统中的应用场景1配置对象例如系统配置MapString,String加载后不应该改变。使用Map.copyOf(config)场景2权限集合用户权限SetRole认证完成冻结。Set.copyOf(roles)场景3领域对象订单Order{ ListOrderItem }订单创建后商品列表不能随便被修改。十四、集合封装设计错误class ShoppingCart{ private ListItem items; public ListItem getItems(){ return items; } }正确class ShoppingCart{ private final ListItem items; public ListItem getItems(){ return List.copyOf(items); } public void addItem(Item item){ items.add(item); } }核心集合修改只能通过领域方法。例如cart.addItem()而不是cart.getItems().add()十五、为什么这和高质量代码相关低质量代码对象任何地方 ↓ 直接修改字段导致状态不可控。高质量代码对象 ↓ 自己的方法 ↓ 状态变化这就是面向对象封装。十六、Collections.unmodifiable 和 List.copyOf 区别unmodifiableListList.copyOfJava版本Java5Java10类型只读包装真正不可变依赖原集合是否外部修改影响会不会线程安全有限更安全生产新代码优先List.copyOf()十七、不可变集合的性能优势很多人认为不可变一定慢。实际很多时候更快。原因1. 不需要锁多线程读直接访问。2. 可以共享例如配置对象100个线程共享不用复制。3. GC压力低减少临时修改对象。十八、什么时候不要使用不可变集合不是所有地方都适合。例如循环累积ListString result new ArrayList(); for(...){ result.add(); }不要List.of()每次创建新对象。适合生命周期稳定的数据。十九、P7 设计案例案例设计用户权限模型。需求用户登录后权限不会频繁变化。错误class User{ SetRole roles; }暴露getRoles()设计class User{ private final SetRole roles; public SetRole getRoles(){ return Set.copyOf(roles); } }效果外部不能破坏状态多线程读取安全状态边界清晰二十、本篇集合体系总结整个 JDK 集合体系List解决有序数据代表ArrayList LinkedListSet解决唯一数据代表HashSet TreeSetMap解决Key-Value映射代表HashMap ConcurrentHashMap TreeMapQueue解决任务流转代表ArrayDeque BlockingQueue PriorityQueueImmutable Collection解决状态保护代表List.copyOf() Map.copyOf() Set.copyOf()二十一、本章 P7 能力标准看到一个对象内部有 List需要暴露给外部。不会return list;而会考虑List.copyOf(list)看到多线程共享配置。知道不可变对象天然适合。看到DDD 聚合内部集合。知道集合不能直接暴露。应该聚合方法 ↓ 修改状态看到API 返回集合。知道需要考虑是否允许修改是否需要副本是否需要不可变

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价