封装的核心是控制而非隐藏,需通过校验setter、限制getter/setter语义、绑定数据与行为、采用不可变性及防御性拷贝来实现责任边界的清晰划分。

封装不是给字段加个 private 就完事了,真正容易出问题的地方,是把“隐藏”当目的,而忘了“控制”才是核心。很多 bug 和后期维护困难,都源于封装设计表面合规、实际失效。
误区一:只设 private,却不校验 setter 行为
把字段设成 private 是起点,不是终点。如果 setter 方法里不做任何校验,等于开了后门——外部仍能塞进非法值。
- 错误写法:
public void setAge(int age) { this.age = age; }(年龄可设为 -5 或 200) - 正确做法:在 setter 中加入业务约束,比如
if (age 150) throw new IllegalArgumentException("年龄必须在0~150之间"); - 更进一步:对关键字段(如账户余额、订单状态)考虑不提供 public setter,改用业务方法驱动变更,例如
pay(double amount)或cancel(),让状态流转受控。
误区二:getter/setter 机械生成,忽略语义与边界
IDE 一键生成的 getter/setter 很方便,但容易掩盖设计问题:不该被读的字段暴露了,不该被写的字段开放了,甚至返回了可变对象的引用。
- 避免直接返回内部集合引用:
public List<item> getItems() { return items; }</item>→ 外部可随意修改原列表。应改为return new ArrayList(items);或Collections.unmodifiableList(items) - 敏感字段(如密码、token)只提供 setter,不提供 getter;或 getter 返回 null/占位符,而非明文
- 只读字段(如创建时间)只提供 getter,不写 setter;必要时用 final 修饰 + 构造器注入
误区三:把类当数据容器,割裂数据与行为
一个只有字段和空壳 getter/setter 的类,本质是结构体(struct),不是对象。封装要求“数据+操作”绑定在同一抽象内。
当用户要求为 Java 代码添加注释、修改注释、审查注释、整理注释格式,或要求按照代码注释规范处理 Java 文件时,使用本技能。本技能用于统一生成和维护 Java 类注释模板、按 Git 对比 release 判断版本号、补充每行代码上方中文块注释、调整注释空行规则,并保持 Java 文件 UTF-8 无 BOM 编码。典型触发场景包括:补充类注释、更新 @version、检查 @author 来源、整理行尾注释、把注释放到代码上一行、检查注释是否符合规范、构建前检查 Java 注释风格。
立即学习“Java免费学习笔记(深入)”;
- 例如订单类中,不要只留
setStatus(int status),而应提供confirm()、ship()、refund()等带业务含义的方法,内部自动校验状态迁移合法性 - 把校验逻辑、状态约束、副作用处理(如发通知、更新统计)都收进类内部,而不是散落在 service 层反复判断
- 一旦发现某个字段总要配合固定几行校验代码一起出现,说明这些逻辑本该属于这个类——把它搬进去
误区四:忽视不可变性与防御性拷贝
封装不只防“恶意篡改”,更要防“无意污染”。尤其涉及日期、集合、数组、自定义对象等可变类型时。
- 接收外部传入的可变对象(如
ArrayList、Date)时,在构造器或 setter 中做防御性拷贝:this.items = new ArrayList(sourceItems); - 返回内部可变对象时,同样做拷贝或包装为不可变视图
- 对时间字段优先使用
LocalDateTime等不可变类型,并用final修饰 - 若整个对象逻辑上不应被修改(如配置项、DTO 响应体),可设计为不可变类:所有字段 final,无 setter,构造器全参数初始化
封装的本质是划清责任边界——谁的数据,谁负责校验、保护和解释。它不是为了增加调用成本,而是为了让每一次访问都安全、可预期、可追溯。写完一个类,不妨自问一句:如果别人只看它的 public 方法,能不能准确理解这个对象能做什么、不能做什么、什么时候会出错?答案越明确,封装就越到位。

















