讲解
上一章的字段都是敞开的:p.age = -500; 这种离谱赋值编译器完全不拦。封装(encapsulation)的思想是「数据私有化,操作公开化」——字段一律声明为 private,外部摸不到;类自己提供 public 的方法(getter 读、setter 写)作为唯一的访问通道,setter 里可以加上校验逻辑,非法值直接拒绝。这样无论多少地方使用这个类,数据的合法性都由类自己兜底,bug 的排查范围从「整个项目」缩到「这个类」。
Java 的访问修饰符有四档,从宽到严:public 任何地方都能访问;protected 同包加子类可见;什么都不写(默认,package-private)仅同包可见;private 仅本类内部可见。入门阶段的实践规则很简单:字段一律 private,方法按需 public,工具方法 internal 用的写 private,protected 和默认级别等接触包结构和继承细节后再用。IDE 都能一键生成 getter/setter,不用手写。
封装的价值不止于校验。它把「怎么存」和「怎么用」解耦:今天 age 用 int 存,明天想改成存生日再现场算年龄,只要 getter 的行为不变,外部代码一行都不用动。这就是所谓的「面向接口编程,隐藏实现细节」。getter/setter 也不是机械的教条——没有合理写场景就别提供 setter,只读字段(如创建时间)只给 getter,这本身就是在表达设计意图。
示例
private 字段加带校验的 setter,非法年龄被拒绝:
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
setAge(age);
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
if (age < 0 || age > 150) {
System.out.println("拒绝非法年龄:" + age);
return;
}
this.age = age;
}
}
public class Main {
public static void main(String[] args) {
Person p = new Person("张三", 20);
p.setAge(30);
System.out.println(p.getName() + ":" + p.getAge() + " 岁");
p.setAge(-5);
System.out.println("校验后仍是:" + p.getAge() + " 岁");
}
}
只给 getter 不给 setter,做出「创建后不可改名」的只读字段:
class User {
private final String username;
private int loginCount;
public User(String username) {
this.username = username;
this.loginCount = 0;
}
public String getUsername() {
return username;
}
public int getLoginCount() {
return loginCount;
}
public void login() {
loginCount++;
System.out.println(username + " 第 " + loginCount + " 次登录");
}
}
public class Main {
public static void main(String[] args) {
User u = new User("admin");
u.login();
u.login();
System.out.println("用户名:" + u.getUsername() + ",登录次数:" + u.getLoginCount());
}
}
常见坑
- 字段图省事写 public:一时方便,等于放弃了所有校验和后续修改的自由。字段 private 是 Java 世界近乎铁律的约定。
- getter 直接把内部可变对象交出去:返回内部的数组或 List 引用,调用方就能绕过 setter 改你的数据。需要时返回副本(如 Arrays.copyOf)。
- setter 里不做任何校验:那它就和 public 字段没有本质区别。setter 的价值在关卡作用,没有校验需求时考虑干脆不提供 setter。
- 在构造方法里直接给字段赋值而绕过校验:本例构造方法特意调用 setAge 复用校验逻辑;绕过它,构造路径就成了校验的后门。
小结
字段私有、方法公开,setter 里做校验,只读字段不给 setter;封装让类自己守住数据的合法性。下一节学习属于「类本身」而非对象的成员:static 关键字。