📖 封装的基本概念
什么是封装?
封装是面向对象编程的四大特性 之一,它将数据(属性)和操作数据的方法(行为)包装在一起,并隐藏内部实现细节,只暴露必要的接口。
封装的核心思想
- 隐藏实现:对外隐藏内部实现细节
- 暴露接口:提供公共方法来访问和修改数据
- 数据保护:防止外部直接访问和修改内部数据
🎯 封装的好处
java
arduino
// ❌ 没有封装的问题
public class BadDesign {
public String name; // 直接暴露属性
public int age; // 外部可以直接修改
public double salary; // 没有验证逻辑
}
// ✅ 使用封装的好处
public class GoodDesign {
private String name; // 私有属性,隐藏实现
private int age; // 只能通过方法访问
private double salary; // 可以添加验证逻辑
// 提供公共的访问方法(getter)
public String getName() {
return name;
}
// 提供公共的修改方法(setter)
public void setName(String name) {
// 可以添加验证逻辑
if (name != null && !name.trim().isEmpty()) {
this.name = name;
}
}
}
🔒 访问修饰符(Access Modifiers)
四种访问级别
| 修饰符 | 同类 | 同包 | 子类 | 不同包 | 说明 |
|---|---|---|---|---|---|
| private | ✅ | ❌ | ❌ | ❌ | 仅本类可见 |
| 默认 | ✅ | ✅ | ❌ | ❌ | 同包可见 |
| protected | ✅ | ✅ | ✅ | ❌ | 同包或子类可见 |
| public | ✅ | ✅ | ✅ | ✅ | 所有类可见 |
访问修饰符示例
java
csharp
package com.example;
public class AccessDemo {
// 1. private - 仅本类可见
private String privateField = "私有字段";
// 2. 默认(包私有)- 同包可见
String defaultField = "默认字段";
// 3. protected - 同包或子类可见
protected String protectedField = "受保护字段";
// 4. public - 所有地方可见
public String publicField = "公共字段";
// 访问私有字段的方法
public String getPrivateField() {
return privateField; // 本类中可以访问private
}
private void privateMethod() {
System.out.println("私有方法");
}
public void testAccess() {
// 所有访问级别在本类中都可见
System.out.println(privateField);
System.out.println(defaultField);
System.out.println(protectedField);
System.out.println(publicField);
privateMethod();
}
}
// 同包中的另一个类
package com.example;
class SamePackageClass {
public void test() {
AccessDemo demo = new AccessDemo();
// System.out.println(demo.privateField); // ❌ 错误:不可访问
System.out.println(demo.defaultField); // ✅ 可以:同包
System.out.println(demo.protectedField); // ✅ 可以:同包
System.out.println(demo.publicField); // ✅ 可以:公共
}
}
// 不同包中的类
package other;
import com.example.AccessDemo;
class DifferentPackageClass {
public void test() {
AccessDemo demo = new AccessDemo();
// System.out.println(demo.privateField); // ❌ 错误
// System.out.println(demo.defaultField); // ❌ 错误:不同包
// System.out.println(demo.protectedField); // ❌ 错误:不同包非子类
System.out.println(demo.publicField); // ✅ 可以:公共
// 通过公共方法访问私有字段
System.out.println(demo.getPrivateField()); // ✅ 可以
}
}
// 不同包中的子类
package other;
import com.example.AccessDemo;
class SubClass extends AccessDemo {
public void test() {
// System.out.println(privateField); // ❌ 错误
// System.out.println(defaultField); // ❌ 错误:不同包
System.out.println(protectedField); // ✅ 可以:子类
System.out.println(publicField); // ✅ 可以:公共
// 通过继承的方法访问
System.out.println(getPrivateField()); // ✅ 可以
}
}
🛠️ Getter 和 Setter 方法
标准Getter/Setter模式
java
typescript
public class Student {
// 私有字段
private String name;
private int age;
private String studentId;
private double gpa;
// 1. Getter方法:获取字段值
// 命名规范:get + 字段名(首字母大写)
public String getName() {
return name;
}
public int getAge() {
return age;
}
public String getStudentId() {
return studentId;
}
public double getGpa() {
return gpa;
}
// 2. Setter方法:设置字段值
// 命名规范:set + 字段名(首字母大写)
public void setName(String name) {
// 可以添加验证逻辑
if (name != null && name.length() >= 2) {
this.name = name;
} else {
System.out.println("姓名无效");
}
}
public void setAge(int age) {
// 验证年龄范围
if (age >= 0 && age <= 150) {
this.age = age;
} else {
System.out.println("年龄必须在0-150之间");
}
}
public void setStudentId(String studentId) {
// 验证学号格式
if (studentId != null && studentId.matches("\d{10}")) {
this.studentId = studentId;
} else {
System.out.println("学号必须是10位数字");
}
}
public void setGpa(double gpa) {
// 验证GPA范围
if (gpa >= 0.0 && gpa <= 4.0) {
this.gpa = gpa;
} else {
System.out.println("GPA必须在0.0-4.0之间");
}
}
// 3. 只读字段(只有getter,没有setter)
private final String createdAt = java.time.LocalDate.now().toString();
public String getCreatedAt() {
return createdAt;
}
// 4. 只写字段(不推荐,但有特殊用途)
private String password;
public void setPassword(String password) {
this.password = password;
}
// 5. 布尔类型的Getter规范
private boolean graduated;
// 布尔类型使用is前缀
public boolean isGraduated() {
return graduated;
}
public void setGraduated(boolean graduated) {
this.graduated = graduated;
}
// 显示学生信息
public void displayInfo() {
System.out.println("学生信息:");
System.out.println("姓名: " + name);
System.out.println("年龄: " + age);
System.out.println("学号: " + studentId);
System.out.println("GPA: " + gpa);
System.out.println("创建时间: " + createdAt);
System.out.println("是否毕业: " + graduated);
}
public static void main(String[] args) {
Student student = new Student();
// 使用setter设置值(有验证)
student.setName("张三");
student.setAge(20);
student.setStudentId("2023000001");
student.setGpa(3.8);
student.setGraduated(false);
// 使用getter获取值
System.out.println("学生姓名: " + student.getName());
System.out.println("学生年龄: " + student.getAge());
// 测试无效输入
student.setAge(200); // 会输出错误信息
student.setGpa(5.0); // 会输出错误信息
student.displayInfo();
}
}
高级Getter/Setter技巧
java
java
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class AdvancedGetterSetter {
private String name;
private List<String> hobbies; // 可变对象
private int[] scores; // 数组
private final String id; // final字段
// 构造器初始化final字段
public AdvancedGetterSetter(String id) {
this.id = id;
this.hobbies = new ArrayList<>();
this.scores = new int[3];
}
// 1. 返回不可修改的集合(防御性编程)
public List<String> getHobbies() {
// 返回副本,防止外部修改内部数据
return Collections.unmodifiableList(new ArrayList<>(hobbies));
// 或者返回只读视图
// return Collections.unmodifiableList(hobbies);
}
// 2. 控制集合的修改
public void addHobby(String hobby) {
if (hobby != null && !hobby.trim().isEmpty()) {
this.hobbies.add(hobby);
}
}
public void removeHobby(String hobby) {
this.hobbies.remove(hobby);
}
// 3. 数组的Getter/Setter
public int[] getScores() {
// 返回数组的副本
return scores.clone();
}
public void setScores(int[] scores) {
// 复制数组,而不是直接引用
System.arraycopy(scores, 0, this.scores, 0,
Math.min(scores.length, this.scores.length));
}
// 4. 只读的final字段
public String getId() {
return id;
}
// 5. 计算性属性(没有对应的字段)
public double getAverageScore() {
if (scores.length == 0) return 0.0;
int sum = 0;
for (int score : scores) {
sum += score;
}
return (double) sum / scores.length;
}
// 6. 链式Setter(返回this)
public AdvancedGetterSetter setName(String name) {
this.name = name;
return this;
}
// 7. Builder模式结合
public static class Builder {
private String id;
private String name;
public Builder(String id) {
this.id = id;
}
public Builder name(String name) {
this.name = name;
return this;
}
public AdvancedGetterSetter build() {
AdvancedGetterSetter obj = new AdvancedGetterSetter(id);
obj.name = name;
return obj;
}
}
public static void main(String[] args) {
AdvancedGetterSetter obj = new AdvancedGetterSetter("001");
// 测试集合保护
obj.addHobby("篮球");
obj.addHobby("音乐");
List<String> hobbies = obj.getHobbies();
System.out.println("爱好: " + hobbies);
// hobbies.add("游泳"); // ❌ 运行时异常:UnsupportedOperationException
// 测试数组保护
int[] testScores = {90, 85, 95};
obj.setScores(testScores);
int[] scores = obj.getScores();
scores[0] = 0; // 修改的是副本,不影响原数组
System.out.println("平均分: " + obj.getAverageScore());
// 链式调用
AdvancedGetterSetter obj2 = new AdvancedGetterSetter("002")
.setName("李四");
}
}
🏗️ 封装的实际应用
示例1:银行账户类
java
typescript
public class BankAccount {
// 私有字段
private final String accountNumber; // 账号不可变
private String accountHolder; // 户主姓名
private double balance; // 余额
private String password; // 密码(不提供getter)
private boolean isActive; // 账户状态
// 构造器
public BankAccount(String accountNumber, String accountHolder,
String password, double initialDeposit) {
// 参数验证
validateAccountNumber(accountNumber);
validateAccountHolder(accountHolder);
validatePassword(password);
validateInitialDeposit(initialDeposit);
this.accountNumber = accountNumber;
this.accountHolder = accountHolder;
this.password = password;
this.balance = initialDeposit;
this.isActive = true;
}
// 私有验证方法
private void validateAccountNumber(String accountNumber) {
if (accountNumber == null || !accountNumber.matches("\d{16}")) {
throw new IllegalArgumentException("账号必须是16位数字");
}
}
private void validateAccountHolder(String accountHolder) {
if (accountHolder == null || accountHolder.trim().isEmpty()) {
throw new IllegalArgumentException("户主姓名不能为空");
}
}
private void validatePassword(String password) {
if (password == null || password.length() < 6) {
throw new IllegalArgumentException("密码至少6位");
}
}
private void validateInitialDeposit(double amount) {
if (amount < 0) {
throw new IllegalArgumentException("初始存款不能为负");
}
}
// Getter方法
public String getAccountNumber() {
return accountNumber;
}
public String getAccountHolder() {
return accountHolder;
}
public double getBalance() {
return balance;
}
public boolean isActive() {
return isActive;
}
// Setter方法(有限制)
public void setAccountHolder(String accountHolder) {
validateAccountHolder(accountHolder);
this.accountHolder = accountHolder;
}
// 业务方法(封装业务逻辑)
public void deposit(double amount) {
if (!isActive) {
System.out.println("账户已冻结,无法存款");
return;
}
if (amount <= 0) {
System.out.println("存款金额必须大于0");
return;
}
balance += amount;
System.out.printf("存款成功!存入¥%.2f,当前余额¥%.2f%n", amount, balance);
}
public boolean withdraw(double amount, String password) {
if (!isActive) {
System.out.println("账户已冻结,无法取款");
return false;
}
if (!authenticate(password)) {
System.out.println("密码错误");
return false;
}
if (amount <= 0) {
System.out.println("取款金额必须大于0");
return false;
}
if (amount > balance) {
System.out.println("余额不足");
return false;
}
balance -= amount;
System.out.printf("取款成功!取出¥%.2f,当前余额¥%.2f%n", amount, balance);
return true;
}
public boolean transfer(BankAccount target, double amount, String password) {
if (!isActive || !target.isActive) {
System.out.println("账户状态异常,无法转账");
return false;
}
if (!authenticate(password)) {
System.out.println("密码错误");
return false;
}
if (withdraw(amount, password)) {
target.deposit(amount);
System.out.printf("向%s转账¥%.2f成功%n",
target.getAccountHolder(), amount);
return true;
}
return false;
}
// 私有辅助方法
private boolean authenticate(String password) {
return this.password.equals(password);
}
public void changePassword(String oldPassword, String newPassword) {
if (authenticate(oldPassword)) {
validatePassword(newPassword);
this.password = newPassword;
System.out.println("密码修改成功");
} else {
System.out.println("原密码错误");
}
}
// 账户管理方法
public void activate() {
this.isActive = true;
System.out.println("账户已激活");
}
public void deactivate() {
this.isActive = false;
System.out.println("账户已冻结");
}
// 显示账户信息(隐藏敏感信息)
public void displayAccountInfo() {
System.out.println("\n=== 账户信息 ===");
System.out.println("账号: " + accountNumber);
System.out.println("户主: " + accountHolder);
System.out.printf("余额: ¥%.2f%n", balance);
System.out.println("状态: " + (isActive ? "正常" : "冻结"));
System.out.println("================\n");
}
public static void main(String[] args) {
// 创建账户
BankAccount account1 = new BankAccount(
"6228480012345678",
"张三",
"123456",
1000.00
);
BankAccount account2 = new BankAccount(
"6228480098765432",
"李四",
"654321",
500.00
);
// 测试功能
account1.displayAccountInfo();
account1.deposit(500.00);
account1.withdraw(200.00, "123456");
account1.withdraw(200.00, "错误密码"); // 密码错误
account1.transfer(account2, 300.00, "123456");
account1.changePassword("123456", "新密码");
account1.deactivate();
account1.deposit(100.00); // 账户冻结,无法存款
}
}
⚠️ 封装的常见错误
错误1:过度暴露内部实现
java
csharp
// ❌ 错误的封装
public class BadEncapsulation {
public List<String> items; // 直接暴露内部集合
public BadEncapsulation() {
items = new ArrayList<>();
}
}
// 使用这个类的代码可以这样做:
BadEncapsulation bad = new BadEncapsulation();
bad.items.add("直接修改"); // 绕过所有验证逻辑
bad.items = null; // 甚至可以破坏内部状态
// ✅ 正确的封装
public class GoodEncapsulation {
private List<String> items;
public GoodEncapsulation() {
items = new ArrayList<>();
}
public void addItem(String item) {
if (item != null && !item.trim().isEmpty()) {
items.add(item);
}
}
public List<String> getItems() {
return Collections.unmodifiableList(new ArrayList<>(items));
}
}
错误2:Getter/Setter中的逻辑过于复杂
java
typescript
// ❌ 不好的做法:在getter/setter中做太多事情
public class ComplexGetterSetter {
private String data;
public String getData() {
// 不应该在getter中修改状态
this.data = processData(this.data); // ❌ 副作用
logAccess(); // ❌ 副作用
return data;
}
public void setData(String data) {
// setter应该只做验证和赋值
validateData(data); // ✅ 可以
logModification(); // ⚠️ 小心:可能有副作用
sendNotification(); // ❌ 不应该
updateDatabase(); // ❌ 不应该
this.data = data;
}
}
// ✅ 好的做法:保持简单
public class SimpleGetterSetter {
private String data;
public String getData() {
return data; // 简单返回
}
public void setData(String data) {
if (isValid(data)) {
this.data = data;
}
}
// 复杂的业务逻辑放在单独的方法中
public void updateDataWithNotification(String data) {
setData(data);
sendNotification();
updateDatabase();
}
}
💡 封装的最佳实践
实践1:使用不可变对象
java
arduino
// 不可变类提供更好的封装
public final class ImmutablePerson {
private final String name; // final字段
private final int age; // final字段
private final List<String> hobbies; // 引用不可变
public ImmutablePerson(String name, int age, List<String> hobbies) {
this.name = name;
this.age = age;
// 防御性拷贝
this.hobbies = Collections.unmodifiableList(new ArrayList<>(hobbies));
}
// 只有getter,没有setter
public String getName() { return name; }
public int getAge() { return age; }
public List<String> getHobbies() {
// 返回不可修改的副本
return Collections.unmodifiableList(new ArrayList<>(hobbies));
}
// 修改操作返回新对象
public ImmutablePerson withName(String newName) {
return new ImmutablePerson(newName, age, hobbies);
}
public ImmutablePerson withAge(int newAge) {
return new ImmutablePerson(name, newAge, hobbies);
}
}
实践2:使用Builder模式
java
arduino
public class Config {
private final String host;
private final int port;
private final int timeout;
private final boolean sslEnabled;
// 私有构造器
private Config(Builder builder) {
this.host = builder.host;
this.port = builder.port;
this.timeout = builder.timeout;
this.sslEnabled = builder.sslEnabled;
}
// Getter
public String getHost() { return host; }
public int getPort() { return port; }
public int getTimeout() { return timeout; }
public boolean isSslEnabled() { return sslEnabled; }
// Builder
public static class Builder {
// 必填参数
private final String host;
private final int port;
// 可选参数(默认值)
private int timeout = 5000;
private boolean sslEnabled = false;
public Builder(String host, int port) {
this.host = host;
this.port = port;
}
public Builder timeout(int timeout) {
this.timeout = timeout;
return this;
}
public Builder sslEnabled(boolean sslEnabled) {
this.sslEnabled = sslEnabled;
return this;
}
public Config build() {
// 验证逻辑
if (host == null || host.trim().isEmpty()) {
throw new IllegalArgumentException("Host不能为空");
}
if (port <= 0 || port > 65535) {
throw new IllegalArgumentException("端口无效");
}
if (timeout < 0) {
throw new IllegalArgumentException("超时时间不能为负");
}
return new Config(this);
}
}
public static void main(String[] args) {
Config config = new Config.Builder("localhost", 8080)
.timeout(10000)
.sslEnabled(true)
.build();
}
}
实践3:使用接口进一步封装
java
java
// 接口定义契约
public interface UserRepository {
User findById(String id);
void save(User user);
void delete(String id);
List<User> findAll();
}
// 实现类隐藏实现细节
public class DatabaseUserRepository implements UserRepository {
private DataSource dataSource;
private final String tableName = "users";
// 隐藏数据库连接细节
public DatabaseUserRepository(DataSource dataSource) {
this.dataSource = dataSource;
}
@Override
public User findById(String id) {
// 复杂的数据库查询逻辑
try (Connection conn = dataSource.getConnection()) {
// SQL查询...
return new User(); // 返回结果
} catch (SQLException e) {
throw new RepositoryException("查询失败", e);
}
}
// 其他实现...
}
// 使用方只关心接口
public class UserService {
private UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User getUser(String id) {
return userRepository.findById(id);
}
}
📊 封装总结表
| 封装级别 | 实现方式 | 优点 | 缺点 |
|---|---|---|---|
| 字段私有化 | 使用private修饰字段 | 防止外部直接修改 | 需要提供getter/setter |
| 方法公有化 | 通过public方法访问 | 可以添加验证逻辑 | 可能方法过多 |
| 返回副本 | getter返回对象副本 | 保护内部数据 | 性能开销 |
| 不可变对象 | 使用final字段 | 线程安全,状态固定 | 修改需要创建新对象 |
| 接口封装 | 通过接口暴露功能 | 隐藏实现,易于替换 | 增加复杂度 |
🎓 封装原则总结
- 最小化访问权限:使用最严格的访问修饰符
- 暴露最少接口:只提供必要的方法
- 防御性编程:验证输入,返回副本
- 保持简单:Getter/Setter应该简单直接
- 考虑不可变性:尽可能使用final字段
- 使用接口:进一步隐藏实现细节
- 文档化约束:用文档说明使用限制
记住:封装不是隐藏复杂性,而是管理复杂性。好的封装让类更易于使用、维护和扩展,同时保护内部状态不被意外破坏。