Java 封装(Encapsulation)
封装是面向对象编程的核心特性之一,它将数据和对数据的操作封装在一起,隐藏内部实现细节,只暴露必要的接口。理解封装是编写高质量 Java 代码的基础。本章将详细介绍 Java 中的封装机制。
封装定义
什么是封装
封装(Encapsulation)是将数据和对数据的操作封装在类中,通过访问控制隐藏内部实现细节,只提供必要的接口供外部使用。
核心思想:
- 隐藏实现:内部细节对外不可见
- 提供接口:通过公共方法访问数据
- 保护数据:防止外部直接修改数据
- 简化使用:使用者不需要了解内部实现
封装的实现
通过访问修饰符实现封装:
public class BankAccount {
// 私有属性:隐藏数据
private String accountNumber;
private String ownerName;
private double balance;
// 公共方法:提供接口
public void deposit(double amount) {
if (amount > 0) {
balance += amount;
}
}
public double getBalance() {
return balance;
}
}
private 属性 + getter/setter
私有属性
使用 private 修饰属性,隐藏数据:
public class Student {
// 私有属性:外部不能直接访问
private String name;
private int age;
private double score;
}
Getter 方法
Getter 用于获取私有属性的值。
public class Student {
private String name;
private int age;
private double score;
// Getter 方法:获取姓名
public String getName() {
return name;
}
// Getter 方法:获取年龄
public int getAge() {
return age;
}
// Getter 方法:获取成绩
public double getScore() {
return score;
}
}
命名规范:
- 方法名:
get+ 属性名(首字母大写) - 布尔类型:
is+ 属性名
public class Student {
private boolean active;
// 布尔类型的 Getter
public boolean isActive() {
return active;
}
}
Setter 方法
Setter 用于设置私有属性的值,可以在方法中添加验证逻辑。
public class Student {
private String name;
private int age;
private double score;
// Setter 方法:设置姓名
public void setName(String name) {
if (name != null && !name.trim().isEmpty()) {
this.name = name;
} else {
throw new IllegalArgumentException("姓名不能为空");
}
}
// Setter 方法:设置年龄(带验证)
public void setAge(int age) {
if (age > 0 && age < 150) {
this.age = age;
} else {
throw new IllegalArgumentException("年龄必须在 0-150 之间");
}
}
// Setter 方法:设置成绩(带验证)
public void setScore(double score) {
if (score >= 0 && score <= 100) {
this.score = score;
} else {
throw new IllegalArgumentException("成绩必须在 0-100 之间");
}
}
}
命名规范:
- 方法名:
set+ 属性名(首字母大写) - 返回类型:
void - 参数:属性类型
完整的封装示例
public class BankAccount {
// 私有属性
private String accountNumber;
private String ownerName;
private double balance;
// 构造方法
public BankAccount(String accountNumber, String ownerName) {
this.accountNumber = accountNumber;
this.ownerName = ownerName;
this.balance = 0.0;
}
// Getter 方法
public String getAccountNumber() {
return accountNumber;
}
public String getOwnerName() {
return ownerName;
}
public double getBalance() {
return balance;
}
// Setter 方法(只读属性不需要 Setter)
// accountNumber 和 ownerName 是只读的,不提供 Setter
// 业务方法:存款
public void deposit(double amount) {
if (amount > 0) {
balance += amount;
System.out.println("存款成功,余额:" + balance);
} else {
System.out.println("存款金额必须大于 0");
}
}
// 业务方法:取款
public void withdraw(double amount) {
if (amount > 0 && amount <= balance) {
balance -= amount;
System.out.println("取款成功,余额:" + balance);
} else {
System.out.println("取款失败,余额不足或金额无效");
}
}
// 业务方法:显示信息
public void displayInfo() {
System.out.println("账户号:" + accountNumber);
System.out.println("户主:" + ownerName);
System.out.println("余额:" + balance);
}
}
封装的好处
1. 数据保护
防止外部直接修改数据,保证数据完整性:
// ❌ 不封装:数据可能被破坏
public class Student {
public int age; // 外部可以直接修改
}
Student student = new Student();
student.age = -10; // 无效数据
// ✅ 封装:数据受到保护
public class Student {
private int age;
public void setAge(int age) {
if (age > 0 && age < 150) {
this.age = age;
} else {
throw new IllegalArgumentException("年龄无效");
}
}
}
Student student = new Student();
student.setAge(-10); // 抛出异常,数据得到保护
2. 隐藏实现细节
内部实现可以改变,但接口不变:
public class Calculator {
// 内部实现可以改变
public int add(int a, int b) {
// 实现方式 1:直接相加
// return a + b;
// 实现方式 2:使用位运算(内部改变,外部不受影响)
while (b != 0) {
int carry = a & b;
a = a ^ b;
b = carry << 1;
}
return a;
}
}
// 外部使用不受影响
Calculator calc = new Calculator();
int result = calc.add(5, 3); // 无论内部如何实现,结果都是 8
3. 简化使用
使用者不需要了解内部实现:
public class DatabaseConnection {
private String url;
private String username;
private String password;
private Connection connection;
// 封装复杂的连接逻辑
public void connect() {
// 复杂的连接代码
// 使用者不需要知道如何连接
}
public void disconnect() {
// 复杂的断开连接代码
}
}
// 使用简单
DatabaseConnection db = new DatabaseConnection();
db.connect(); // 不需要知道内部如何连接
4. 便于维护
修改内部实现不影响外部代码:
public class Student {
// 原来:使用数组存储课程
// private String[] courses;
// 现在:改为使用 List(内部改变)
private List<String> courses;
// 接口不变
public void addCourse(String course) {
courses.add(course);
}
public List<String> getCourses() {
return new ArrayList<>(courses); // 返回副本,保护内部数据
}
}
5. 提高代码质量
通过封装实现更好的代码组织:
public class BankAccount {
private double balance;
// 封装业务逻辑
public void deposit(double amount) {
validateAmount(amount);
balance += amount;
logTransaction("DEPOSIT", amount);
notifyUser("存款成功");
}
// 私有方法:内部使用
private void validateAmount(double amount) {
if (amount <= 0) {
throw new IllegalArgumentException("金额必须大于 0");
}
}
private void logTransaction(String type, double amount) {
// 记录交易日志
}
private void notifyUser(String message) {
// 通知用户
}
}
封装的最佳实践
1. 属性使用 private
public class Student {
// ✅ 推荐:私有属性
private String name;
private int age;
// ❌ 不推荐:公共属性
// public String name;
}
2. 提供 Getter/Setter
public class Student {
private String name;
// ✅ 提供 Getter
public String getName() {
return name;
}
// ✅ 提供 Setter(如果需要修改)
public void setName(String name) {
this.name = name;
}
}
3. 在 Setter 中添加验证
public class Student {
private int age;
public void setAge(int age) {
// ✅ 添加验证
if (age > 0 && age < 150) {
this.age = age;
} else {
throw new IllegalArgumentException("年龄无效");
}
}
}
4. 返回集合的副本
public class Student {
private List<String> courses = new ArrayList<>();
// ✅ 返回副本,保护内部数据
public List<String> getCourses() {
return new ArrayList<>(courses);
}
// ❌ 不推荐:直接返回内部集合
// public List<String> getCourses() {
// return courses; // 外部可以修改内部数据
// }
}
实际示例
示例 1:完整的封装实现
public class Employee {
// 私有属性
private String id;
private String name;
private double salary;
private String department;
// 构造方法
public Employee(String id, String name, double salary, String department) {
setId(id);
setName(name);
setSalary(salary);
setDepartment(department);
}
// Getter 方法
public String getId() {
return id;
}
public String getName() {
return name;
}
public double getSalary() {
return salary;
}
public String getDepartment() {
return department;
}
// Setter 方法(带验证)
public void setId(String id) {
if (id != null && !id.trim().isEmpty()) {
this.id = id;
} else {
throw new IllegalArgumentException("ID 不能为空");
}
}
public void setName(String name) {
if (name != null && !name.trim().isEmpty()) {
this.name = name;
} else {
throw new IllegalArgumentException("姓名不能为空");
}
}
public void setSalary(double salary) {
if (salary >= 0) {
this.salary = salary;
} else {
throw new IllegalArgumentException("薪资不能为负数");
}
}
public void setDepartment(String department) {
if (department != null && !department.trim().isEmpty()) {
this.department = department;
} else {
throw new IllegalArgumentException("部门不能为空");
}
}
// 业务方法
public void raiseSalary(double percentage) {
if (percentage > 0) {
salary += salary * percentage / 100;
}
}
public void displayInfo() {
System.out.println("员工 ID:" + id);
System.out.println("姓名:" + name);
System.out.println("薪资:" + salary);
System.out.println("部门:" + department);
}
}
示例 2:只读属性
public class Circle {
// 只读属性:半径(通过构造方法设置)
private final double radius;
public Circle(double radius) {
if (radius > 0) {
this.radius = radius;
} else {
throw new IllegalArgumentException("半径必须大于 0");
}
}
// 只提供 Getter,不提供 Setter
public double getRadius() {
return radius;
}
// 计算面积
public double getArea() {
return Math.PI * radius * radius;
}
// 计算周长
public double getCircumference() {
return 2 * Math.PI * radius;
}
}
小结
Java 封装要点:
- 封装定义:隐藏实现,提供接口
- 实现方式:private 属性 + getter/setter
- 好处:数据保护、隐藏细节、简化使用、便于维护
- 最佳实践:属性私有、提供访问方法、添加验证
关键要点:
- 使用 private 隐藏数据
- 通过 getter/setter 访问数据
- 在 setter 中添加验证逻辑
- 返回集合时返回副本
- 只读属性不提供 setter
理解了封装,你就能编写更安全、更易维护的代码。在下一章,我们将学习 Java 的继承。