封装是系统安全边界的首道工程防线,通过private字段+public getter/setter统一管控访问,在set方法中校验、通知、联动;配合final字段与构造器约束杜绝非法对象;访问修饰符需按语义契约而非包路径划分边界;返回可变集合时须防御性复制。

封装不是加个 private 就完事了,它本质是系统安全边界的**第一道工程防线**——边界划在哪,决定了哪些代码能碰数据、哪些调用会触发校验、哪些变更对外透明或不可见。
封装直接定义“谁可以读写什么字段”
Java 中 private 字段 + public getter/setter 不是形式主义,而是把数据访问权收归类内部统一管控。一旦字段暴露为 public,任何外部代码都能绕过所有验证逻辑直接赋值,比如:
user.age = -5; // 合法?不合法。但 public int age; 允许它发生
而封装后,你可以在 setAge(int age) 里强制校验:
- 检查是否在合理范围(如 0–150)
- 触发状态变更通知(如
firePropertyChange("age", old, new)) - 联动其他字段(如年龄变化时重算会员等级)
这些逻辑一旦脱离封装体,就没人能保证每次赋值都走一遍。
封装让“非法状态”无法构造出来
对象不是数据容器,而是**状态+约束的统一体**。没有封装,你很容易写出这样的代码:
User u = new User();<br>u.name = null;<br>u.email = "invalid@";<br>u.id = 0;
这个 u 已经是非法对象,但它能被创建、能被传参、能进数据库。而封装配合构造器约束(如 requireNonNull(name))和 final 字段,能把非法实例从源头堵住:
-
final String name确保构造后不可变 - 构造器抛出
IllegalArgumentException拦截空邮箱 - 不提供无参构造器,避免“半初始化”对象流出
这才是真正把安全边界前移到对象诞生那一刻。
封装隔离的是变化,不是代码位置
很多人以为同包下用默认访问(package-private)就等于“安全”,其实不然。只要类在同一个包,哪怕来自不同模块、不同团队、不同发布周期的代码,都能直接访问该字段。这等于把安全边界画在了文件系统路径上,而不是语义契约上。
真正可靠的隔离必须依赖访问修饰符层级:
-
private:只对本类有效,最严,适合敏感状态(如密码哈希、连接池句柄) -
protected:仅限子类+同包,适合框架扩展点(如 Spring 的AbstractApplicationContext中的模板方法钩子) -
public:必须是稳定契约,一旦发布就不能轻易改签名,否则就是破坏性变更
边界划错一层,就可能把内部实现细节(比如用了 ArrayList 还是 LinkedList)意外暴露出去,后续想换实现就动弹不得。
最容易被忽略的一点:封装失效往往不在字段本身,而在返回值。比如 getRoles() 返回一个可变的 ArrayList,外部拿到后直接 add(),就等于绕过了类内部的所有角色管理逻辑。这时候得用 Collections.unmodifiableList() 或返回副本——安全边界要守到最后一行返回语句。

















