Appearance
11.1 封装(private、get/set 方法)
封装的概念
封装(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 | 私有的,只有本类可以访问 | 属性、方法 |
getter 和 setter 方法
getter 方法
getter 方法用于获取属性的值,方法名通常以 get 开头,后跟属性名(首字母大写)。
语法:
java
public 返回类型 get属性名() {
return 属性名;
}示例:
java
public String getName() {
return name;
}
public int getAge() {
return age;
}setter 方法
setter 方法用于设置属性的值,方法名通常以 set 开头,后跟属性名(首字母大写)。
语法:
java
public void set属性名(参数类型 参数名) {
this.属性名 = 参数名;
}示例:
java
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");
}
}示例:封装的使用
示例 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 Employee {
// 私有属性
private String name;
private int employeeId;
private double salary;
// 构造方法
public Employee(String name, int employeeId, double salary) {
this.name = name;
this.employeeId = employeeId;
setSalary(salary); // 使用 setter 方法设置工资,以便进行验证
}
// 公共的 setter 方法
public void setName(String name) {
this.name = name;
}
public void setEmployeeId(int employeeId) {
this.employeeId = employeeId;
}
public void setSalary(double salary) {
// 验证工资的有效性
if (salary >= 0) {
this.salary = salary;
} else {
this.salary = 0;
System.out.println("Salary cannot be negative");
}
}
// 公共的 getter 方法
public String getName() {
return name;
}
public int getEmployeeId() {
return employeeId;
}
public double getSalary() {
return salary;
}
// 其他方法
public void raiseSalary(double percentage) {
if (percentage > 0) {
salary += salary * percentage / 100;
System.out.println("Salary raised by " + percentage + "%");
System.out.println("New salary: " + salary);
} else {
System.out.println("Percentage must be positive");
}
}
}
public class EmployeeExample {
public static void main(String[] args) {
// 创建员工对象
Employee employee = new Employee("John Doe", 1001, 5000.0);
// 查看员工信息
System.out.println("Name: " + employee.getName());
System.out.println("Employee ID: " + employee.getEmployeeId());
System.out.println("Salary: " + employee.getSalary());
// 涨工资
employee.raiseSalary(10); // 涨 10%
// 修改员工信息
employee.setName("Jane Smith");
employee.setSalary(6000.0);
// 查看更新后的员工信息
System.out.println("\nUpdated Employee Info:");
System.out.println("Name: " + employee.getName());
System.out.println("Salary: " + employee.getSalary());
}
}封装的优势
- 数据保护:通过私有属性和公共方法,保护数据不被直接修改
- 代码可维护性:修改内部实现时,不会影响外部代码
- 代码安全性:可以在 setter 方法中添加验证逻辑,确保数据的有效性
- 代码简洁性:外部代码只需要关注公共接口,不需要了解内部实现
- 代码可扩展性:可以在不影响外部代码的情况下,扩展和修改内部实现
封装的最佳实践
- 将属性设为 private:使用 private 访问修饰符,使属性只能在本类中访问
- 提供公共的 getter 和 setter 方法:通过公共方法来访问和修改属性
- 在 setter 方法中添加验证逻辑:确保数据的有效性
- 保持 getter 和 setter 方法的简洁性:只做必要的操作
- 避免在 getter 方法中返回可变对象的引用:如果返回可变对象,应该返回其副本
- 使用命名规范:getter 和 setter 方法应该遵循命名规范,提高代码的可读性
示例:避免返回可变对象的引用
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. 返回可变对象的引用
症状:外部代码可以直接修改对象的内部状态
解决方案:返回可变对象的副本,而不是引用
5. 命名不规范
症状:getter 和 setter 方法的命名不符合规范,影响代码的可读性
解决方案:遵循命名规范,getter 方法以 get 开头,setter 方法以 set 开头,后跟属性名(首字母大写)
总结
封装是面向对象编程的三大特性之一,它将数据和方法封装在对象中,对外只暴露必要的接口,隐藏实现细节。
封装的实现主要通过以下方式:
- 将属性设为 private
- 提供公共的 getter 和 setter 方法
封装的优势包括:
- 保护数据
- 隐藏实现细节
- 提高代码的可维护性
- 提高代码的安全性
- 提高代码的可扩展性
通过合理使用封装,可以使代码更加安全、可维护和可扩展。
