Appearance
10.6 封装概念入门
封装的概念
封装(Encapsulation)是面向对象编程的核心特性之一,它将数据和方法封装在对象中,对外只暴露必要的接口,隐藏实现细节。
封装的作用
- 保护数据:防止外部代码直接访问和修改对象的内部数据
- 隐藏实现细节:只暴露必要的接口,简化外部代码的使用
- 提高代码的可维护性:修改内部实现时,不会影响外部代码
- 提高代码的安全性:防止不合理的操作和数据修改
封装的实现
在 Java 中,封装的实现主要通过以下方式:
- 将属性设为 private:使用 private 访问修饰符,使属性只能在本类中访问
- 提供公共的 getter 和 setter 方法:通过公共方法来访问和修改属性
示例:基本封装
java
public class Person {
// 将属性设为 private
private String name;
private int age;
// 提供公共的 setter 方法
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
// 可以在 setter 方法中添加验证逻辑
if (age >= 0 && age <= 150) {
this.age = age;
} else {
System.out.println("Invalid age");
}
}
// 提供公共的 getter 方法
public String getName() {
return name;
}
public int getAge() {
return age;
}
}访问修饰符
Java 提供了四种访问修饰符,用于控制类、属性和方法的访问权限:
| 修饰符 | 访问权限 | 适用范围 |
|---|---|---|
| public | 公共的,任何类都可以访问 | 类、属性、方法 |
| protected | 受保护的,同一个包或子类可以访问 | 属性、方法 |
| default | 默认的,同一个包可以访问 | 类、属性、方法 |
| private | 私有的,只有本类可以访问 | 属性、方法 |
示例:封装的使用
示例 1:基本使用
java
public class Student {
// 私有属性
private String name;
private int studentId;
private double grade;
// 公共的 setter 方法
public void setName(String name) {
this.name = name;
}
public void setStudentId(int studentId) {
this.studentId = studentId;
}
public void setGrade(double grade) {
// 验证成绩的有效性
if (grade >= 0 && grade <= 100) {
this.grade = grade;
} else {
System.out.println("Invalid grade");
}
}
// 公共的 getter 方法
public String getName() {
return name;
}
public int getStudentId() {
return studentId;
}
public double getGrade() {
return grade;
}
// 其他方法
public boolean isPass() {
return grade >= 60;
}
}
public class StudentExample {
public static void main(String[] args) {
// 创建学生对象
Student student = new Student();
// 使用 setter 方法设置属性
student.setName("John");
student.setStudentId(1001);
student.setGrade(85.5);
// 使用 getter 方法获取属性
System.out.println("Name: " + student.getName());
System.out.println("Student ID: " + student.getStudentId());
System.out.println("Grade: " + student.getGrade());
System.out.println("Pass: " + student.isPass());
// 尝试设置无效的成绩
student.setGrade(150); // 会输出 "Invalid grade"
}
}示例 2:封装的验证
java
public class BankAccount {
// 私有属性
private String accountNumber;
private double balance;
// 构造方法
public BankAccount(String accountNumber, double balance) {
this.accountNumber = accountNumber;
// 验证余额的有效性
if (balance >= 0) {
this.balance = balance;
} else {
this.balance = 0;
System.out.println("Initial balance cannot be negative");
}
}
// 公共的 setter 方法
public void setAccountNumber(String accountNumber) {
this.accountNumber = accountNumber;
}
// 公共的 getter 方法
public String getAccountNumber() {
return accountNumber;
}
public double getBalance() {
return balance;
}
// 其他方法
public void deposit(double amount) {
if (amount > 0) {
balance += amount;
System.out.println("Deposited: " + amount);
System.out.println("New balance: " + balance);
} else {
System.out.println("Deposit amount must be positive");
}
}
public void withdraw(double amount) {
if (amount > 0 && amount <= balance) {
balance -= amount;
System.out.println("Withdrawn: " + amount);
System.out.println("New balance: " + balance);
} else if (amount <= 0) {
System.out.println("Withdrawal amount must be positive");
} else {
System.out.println("Insufficient balance");
}
}
}
public class BankAccountExample {
public static void main(String[] args) {
// 创建银行账户对象
BankAccount account = new BankAccount("123456789", 1000.0);
// 查看初始余额
System.out.println("Account Number: " + account.getAccountNumber());
System.out.println("Initial Balance: " + account.getBalance());
// 存款
account.deposit(500.0);
// 取款
account.withdraw(200.0);
// 尝试取超出余额的金额
account.withdraw(2000.0);
// 尝试存入负数
account.deposit(-100.0);
}
}示例 3:封装的继承
java
public class Person {
// 私有属性
private String name;
private int age;
// 公共的 setter 方法
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
if (age >= 0 && age <= 150) {
this.age = age;
} else {
System.out.println("Invalid age");
}
}
// 公共的 getter 方法
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
public class Student extends Person {
// 私有属性
private int studentId;
private double grade;
// 公共的 setter 方法
public void setStudentId(int studentId) {
this.studentId = studentId;
}
public void setGrade(double grade) {
if (grade >= 0 && grade <= 100) {
this.grade = grade;
} else {
System.out.println("Invalid grade");
}
}
// 公共的 getter 方法
public int getStudentId() {
return studentId;
}
public double getGrade() {
return grade;
}
// 其他方法
public boolean isPass() {
return grade >= 60;
}
}
public class StudentExample {
public static void main(String[] args) {
// 创建学生对象
Student student = new Student();
// 设置属性
student.setName("John");
student.setAge(18);
student.setStudentId(1001);
student.setGrade(85.5);
// 获取属性
System.out.println("Name: " + student.getName());
System.out.println("Age: " + student.getAge());
System.out.println("Student ID: " + student.getStudentId());
System.out.println("Grade: " + student.getGrade());
System.out.println("Pass: " + student.isPass());
}
}封装的优势
- 数据保护:通过私有属性和公共方法,保护数据不被直接修改
- 代码可维护性:修改内部实现时,不会影响外部代码
- 代码安全性:可以在 setter 方法中添加验证逻辑,确保数据的有效性
- 代码简洁性:外部代码只需要关注公共接口,不需要了解内部实现
- 代码可扩展性:可以在不影响外部代码的情况下,扩展和修改内部实现
封装的最佳实践
- 将属性设为 private:使用 private 访问修饰符,使属性只能在本类中访问
- 提供公共的 getter 和 setter 方法:通过公共方法来访问和修改属性
- 在 setter 方法中添加验证逻辑:确保数据的有效性
- 保持 getter 和 setter 方法的简洁性:只做必要的操作
- 避免在 getter 方法中返回可变对象的引用:如果返回可变对象,应该返回其副本
示例:避免返回可变对象的引用
java
import java.util.ArrayList;
import java.util.List;
public class Student {
private String name;
private List<String> courses;
public Student(String name) {
this.name = name;
this.courses = new ArrayList<>();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
// 错误:返回可变对象的引用
public List<String> getCourses() {
return courses;
}
// 正确:返回可变对象的副本
public List<String> getCoursesSafe() {
return new ArrayList<>(courses);
}
public void addCourse(String course) {
courses.add(course);
}
public void removeCourse(String course) {
courses.remove(course);
}
}
public class StudentExample {
public static void main(String[] args) {
Student student = new Student("John");
student.addCourse("Math");
student.addCourse("English");
// 错误:直接修改返回的列表
List<String> courses = student.getCourses();
courses.add("Physics"); // 会修改学生的课程列表
System.out.println("Courses: " + student.getCourses());
// 正确:修改副本不会影响原始列表
List<String> coursesSafe = student.getCoursesSafe();
coursesSafe.add("Chemistry"); // 不会修改学生的课程列表
System.out.println("Courses after modifying safe copy: " + student.getCourses());
}
}常见问题
1. 过度封装
症状:提供了过多的 getter 和 setter 方法,导致代码冗余
解决方案:只提供必要的 getter 和 setter 方法,避免过度封装
2. 封装不彻底
症状:部分属性没有设为 private,或者没有提供相应的 getter 和 setter 方法
解决方案:将所有属性设为 private,并提供必要的 getter 和 setter 方法
3. 在 setter 方法中缺少验证
症状:可以设置无效的数据,导致对象状态不正确
解决方案:在 setter 方法中添加验证逻辑,确保数据的有效性
4. 返回可变对象的引用
症状:外部代码可以直接修改对象的内部状态
解决方案:返回可变对象的副本,而不是引用
总结
封装是面向对象编程的核心特性之一,它将数据和方法封装在对象中,对外只暴露必要的接口,隐藏实现细节。
封装的实现主要通过以下方式:
- 将属性设为 private
- 提供公共的 getter 和 setter 方法
封装的优势包括:
- 保护数据
- 隐藏实现细节
- 提高代码的可维护性
- 提高代码的安全性
通过合理使用封装,可以使代码更加安全、可维护和可扩展。
