代码评审中识别封装违规需聚焦设计契约:检查public可变对象字段是否暴露内部状态、package-private成员是否被跨包误用、getter/setter是否做防御性拷贝、非静态内部类是否隐式泄露外层实例。

在代码评审中识别访问修饰符导致的封装违规,核心是看“谁该看到、谁不该看到”是否被破坏——不是语法错误,而是设计契约被绕过。重点不是修饰符写得对不对,而是它是否让内部状态可被意外修改、让调用方误以为安全、或让后续扩展变得僵硬。
检查 public 字段直接暴露可变对象
Java 允许 public 字段,但一旦字段类型是可变对象(如 ArrayList、HashMap、数组、自定义可变类),就等于把内部状态完全敞开。
- 典型反例:public List
tags = new ArrayList(); —— 外部可直接tags.add("x"),内部列表被污染 - 正确做法:字段私有 + 返回不可变视图或副本,例如用
Collections.unmodifiableList(tags)或new ArrayList(tags) - 审查时盯住所有
public非基本类型字段,尤其是集合、数组、日期类(java.util.Date)、StringBuilder等
警惕 package-private(默认)修饰符被跨包误用
没有显式修饰符的成员默认为 package-private,看似“不公开”,实则只要在同一包下就能随意访问和修改。团队常因包结构松散或临时重构,让本不该共享的逻辑被其他模块依赖。
- 常见风险点:工具类中的
static Map, ?> cache、配置类里的String endpoint,都用默认修饰符,结果被测试类或新模块直接读写 - 评审建议:非明确设计为“包内协作”的成员,不要依赖默认修饰符;若需跨包使用,应明确定义接口或提供受控访问方法
- 工具辅助:IDEA 可开启 “Package private field/method used outside package” 检查规则
验证 getter/setter 是否违背封装本意
加了 private 字段 + public getter/setter 不代表封装到位。如果 setter 允许任意值、getter 返回可变对象引用,仍是虚假封装。
- 危险模式:
public void setConfig(Map—— 外部传入的 map 后续修改会直接影响内部状态config) { this.config = config; } - 更隐蔽的问题:
public Date getBirthDate() { return birthDate; }——Date可变,调用方调用setTime()就改了内部值 - 修复方向:setter 做深拷贝或校验;getter 返回防御性副本(如
new Date(birthDate.getTime()))或不可变类型(如LocalDateTime)
扫描内部类/匿名类隐式暴露外层实例
非静态内部类天然持有外部类 this 引用。若该内部类被作为返回值、监听器、线程任务等长期持有,就等于把整个外部类对象“泄露”出去,可能造成内存泄漏或状态污染。
- 典型场景:
return new ActionListener() { public void actionPerformed(...) { outerField++; } };—— 外部类字段可被任意修改 - 审查要点:凡是返回非静态内部类或匿名类实例的方法,确认是否真需要访问外层状态;否则应改为静态内部类 + 显式传参
- 特别注意:Lambda 表达式若捕获了
this或可变实例变量,也存在同样风险
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











