封装与访问修饰符
概述
封装(Encapsulation)是面向对象三大特性之一,核心思想是隐藏对象的内部实现细节,只通过公开的方法与外界交互。在 Java 中,通过访问修饰符(Access Modifier)控制类、成员变量、方法的可见范围,从而实现封装:把不想被外部直接访问的成员设为 private,对外提供受控的 getter/setter 或业务方法。
掌握封装与访问修饰符,是写出可维护、可扩展代码的基础,也是学习 继承 与 多态 的前提(子类可见性依赖 protected 等修饰符)。
为什么需要封装
- 数据安全:防止外部随意修改内部数据(如年龄设为负数),可在 setter 中做校验。
- 可维护性:内部实现(如存储方式)变更时,只要对外方法签名不变,调用方无需修改。
- 易用性:对外只暴露「该暴露」的操作,接口更清晰。
四种访问修饰符
Java 提供四种访问级别,从严格到宽松依次为:
| 修饰符 | 同类 | 同包 | 子类 | 任意位置 |
|---|---|---|---|---|
private | ✓ | ✗ | ✗ | ✗ |
| 默认(不写) | ✓ | ✓ | ✗ | ✗ |
protected | ✓ | ✓ | ✓ | ✗ |
public | ✓ | ✓ | ✓ | ✓ |
- 同类:同一类内部。
- 同包:同一包(package)下的其他类。
- 子类:继承该类的子类(可不在同包)。
- 任意位置:任何能引用到该类型的代码。
提示
「默认」即不写任何访问修饰符,也称包私有(package-private),只有同包内的类能访问。
基本语法
修饰类
public:该类可被任意包使用;文件名必须与 public 类名一致。- 默认(不写):仅同包可访问,常用于包内辅助类。
修饰成员变量与方法
java
public class Example {
private int secret; // 仅本类可访问
int packagePrivate; // 同包可访问(默认)
protected int forSubclass; // 本类、同包、子类可访问
public int open; // 任意位置可访问
public int getSecret() { return secret; }
public void setSecret(int v) { secret = v; }
}使用示例
示例 1:用 private + getter/setter 实现封装
不直接暴露成员变量,通过方法读写,便于在 setter 中做校验。
java
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
setAge(age); // 通过 setter 赋值,统一走校验逻辑
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
// setter 中校验,避免无效数据
public void setAge(int age) {
if (age < 0 || age > 150) {
throw new IllegalArgumentException("年龄应在 0~150 之间");
}
this.age = age;
}
}
// 使用
Person p = new Person("张三", 20);
p.setAge(25); // 合法
// p.setAge(-1); // 抛出 IllegalArgumentException示例 2:只读(无 setter)与不可变
若某字段不应被外部修改,可只提供 getter,不提供 setter;若整个对象创建后不再变,可设计为不可变对象。
java
public class Product {
private final String id; // final 保证引用不变
private String name;
public Product(String id, String name) {
this.id = id;
this.name = name;
}
public String getId() {
return id;
}
// 不提供 setId,id 创建后不可改
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}示例 3:protected 在继承中的使用
子类需要访问父类成员时,可将该成员设为 protected(本类、同包、子类可见)。详见 继承。
java
// 父类
public class Animal {
protected String name;
public Animal(String name) {
this.name = name;
}
}
// 子类(可在另一包)
public class Dog extends Animal {
public Dog(String name) {
super(name);
}
public void bark() {
System.out.println(name + " says: Wang!"); // 可访问 protected name
}
}封装实践要点
getter/setter 命名
- getter:
get + 首字母大写的属性名,布尔类型也可用is + 首字母大写的属性名。 - setter:
set + 首字母大写的属性名。
java
private String userName;
public String getUserName() { return userName; }
public void setUserName(String userName) { this.userName = userName; }
private boolean active;
public boolean isActive() { return active; }
public void setActive(boolean active) { this.active = active; }何时用哪种修饰符
- 成员变量:若无特殊需求,建议
private,通过方法暴露。 - 仅包内使用的类或成员:用默认(不写修饰符)。
- 需要子类访问的成员:用
protected。 - 对外 API(类、方法):用
public。
注意
对成员变量使用 public 会破坏封装,外部可随意修改,一般只在不关心数据安全的简单数据载体或常量(public static final)时使用。
注意事项
- 重写(override)时:子类重写父类方法时,访问权限不能比父类更严格(例如父类
protected,子类不能改为private),可以相同或更宽松。 - 局部变量:方法或块内的局部变量不能加访问修饰符,它们只在当前方法/块内有效。
- 构造方法:可以是
private(如单例)、protected(允许子类调用)或public(常见)。
提示
优先用 private 字段 + 公开方法的方式暴露能力,在 setter 或业务方法中做校验与逻辑,避免「裸」的 public 成员变量。