面向对象编程基础:封装¶
1. 概述¶
封装(Encapsulation)是面向对象编程(OOP)的四大基本原则之一(其他三个是继承、多态和抽象)。封装的主要目的是将对象的内部状态(属性)和行为(方法)隐藏起来,只暴露必要的接口供外部访问。通过封装,我们可以控制对对象内部数据的访问,防止外部代码直接修改对象的内部状态,从而提高代码的安全性和可维护性。
2. 封装的概念¶
2.1 什么是封装?¶
封装是指将对象的属性和方法包装在一起,并对外部隐藏对象的内部实现细节。通过封装,我们可以将对象的内部状态和行为保护起来,只允许通过特定的方法(通常称为“getter”和“setter”)来访问和修改对象的属性。
2.2 封装的好处¶
- 数据隐藏:封装可以隐藏对象的内部实现细节,防止外部代码直接访问或修改对象的内部状态。
- 提高安全性:通过封装,我们可以控制对对象属性的访问,防止非法或不合理的修改。
- 增强可维护性:封装使得对象的内部实现可以独立于外部代码进行修改,而不会影响到使用该对象的其他代码。
- 简化接口:封装可以将复杂的内部实现隐藏起来,只暴露简单的接口供外部使用。
3. 封装的实现方法¶
在Java中,封装主要通过以下方式实现:
- 访问修饰符:使用
private
、protected
、public
等访问修饰符来控制类成员的访问权限。 - getter和setter方法:通过提供公共的
getter
和setter
方法来访问和修改私有属性。
3.1 访问修饰符¶
Java提供了四种访问修饰符来控制类成员的访问权限:
- private:只能在当前类中访问。
- protected:可以在当前类、同一个包中的类以及子类中访问。
- public:可以在任何地方访问。
- 默认(无修饰符):只能在同一个包中访问。
3.2 getter和setter方法¶
getter
方法用于获取私有属性的值,setter
方法用于设置私有属性的值。通过getter
和setter
方法,我们可以在访问或修改属性时添加额外的逻辑,例如数据验证、日志记录等。
4. 代码示例¶
4.1 示例1:基本封装¶
// 定义一个Person类
public class Person {
// 使用private修饰符将属性封装起来
private String name;
private int age;
// 提供公共的getter和setter方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
if (age > 0) { // 在setter方法中添加数据验证
this.age = age;
} else {
System.out.println("年龄不能为负数");
}
}
}
// 测试类
public class Main {
public static void main(String[] args) {
Person person = new Person();
person.setName("Alice");
person.setAge(25);
System.out.println("姓名: " + person.getName());
System.out.println("年龄: " + person.getAge());
}
}
解释: - Person
类中的name
和age
属性被声明为private
,外部代码无法直接访问这些属性。 - 通过getName()
和getAge()
方法可以获取属性的值,通过setName()
和setAge()
方法可以设置属性的值。 - 在setAge()
方法中,我们添加了数据验证逻辑,确保年龄不能为负数。
4.2 示例2:封装与数据验证¶
// 定义一个BankAccount类
public class BankAccount {
private String accountNumber;
private double balance;
public String getAccountNumber() {
return accountNumber;
}
public void setAccountNumber(String accountNumber) {
this.accountNumber = accountNumber;
}
public double getBalance() {
return balance;
}
// 不允许直接设置余额,只能通过存款和取款操作来修改余额
public void deposit(double amount) {
if (amount > 0) {
balance += amount;
} else {
System.out.println("存款金额必须大于0");
}
}
public void withdraw(double amount) {
if (amount > 0 && amount <= balance) {
balance -= amount;
} else {
System.out.println("取款金额无效");
}
}
}
// 测试类
public class Main {
public static void main(String[] args) {
BankAccount account = new BankAccount();
account.setAccountNumber("123456789");
account.deposit(1000);
account.withdraw(500);
System.out.println("账户余额: " + account.getBalance());
}
}
解释: - BankAccount
类中的balance
属性被封装起来,外部代码无法直接修改余额。 - 通过deposit()
和withdraw()
方法来修改余额,并在这些方法中添加了数据验证逻辑。 - 这样可以确保余额不会被非法修改,例如取款金额不能超过当前余额。
4.3 示例3:封装与只读属性¶
// 定义一个Student类
public class Student {
private String name;
private final int studentId; // 使用final修饰符使studentId成为只读属性
public Student(String name, int studentId) {
this.name = name;
this.studentId = studentId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getStudentId() {
return studentId;
}
// 没有setStudentId方法,因为studentId是只读的
}
// 测试类
public class Main {
public static void main(String[] args) {
Student student = new Student("Bob", 12345);
System.out.println("学生姓名: " + student.getName());
System.out.println("学生ID: " + student.getStudentId());
// student.setStudentId(67890); // 这行代码会报错,因为studentId是只读的
}
}
解释: - Student
类中的studentId
属性被声明为final
,这意味着它只能在构造函数中初始化,之后不能被修改。 - 由于没有提供setStudentId()
方法,studentId
属性是只读的,外部代码无法修改它。
5. 练习题¶
5.1 简单练习¶
- 创建一个
Car
类,包含make
、model
和year
三个私有属性。为这些属性提供getter
和setter
方法,并在setYear()
方法中添加数据验证,确保year
不能小于1900。
5.2 中等练习¶
- 创建一个
Employee
类,包含name
、salary
和employeeId
三个私有属性。salary
属性只能通过raiseSalary()
方法来增加,employeeId
属性是只读的。编写一个测试类,创建一个Employee
对象并测试其方法。
5.3 复杂练习¶
- 创建一个
Library
类,包含一个books
列表(List<Book>
),其中Book
是一个包含title
和author
属性的类。Library
类应该提供addBook()
、removeBook()
和findBookByTitle()
方法。确保books
列表不能被外部直接访问,只能通过Library
类提供的方法来操作。
6. 总结¶
- 封装是面向对象编程的基本原则之一,通过封装可以将对象的内部状态和行为隐藏起来,只暴露必要的接口供外部访问。
- 在Java中,封装主要通过访问修饰符(如
private
、public
)和getter/setter方法来实现。 - 封装可以提高代码的安全性、可维护性和灵活性。
- 通过封装,我们可以在访问或修改属性时添加额外的逻辑,例如数据验证、日志记录等。
掌握封装的概念和实现方法对于编写高质量的面向对象代码至关重要。通过合理使用封装,我们可以构建出更加健壮、安全和易于维护的应用程序。