源本科技 | 码上会

Java 中的封装

2025/12/27
35
0

学习目标

  • 理解封装的核心思想:数据与行为的统一 + 信息隐藏

  • 掌握在 Java 中通过 访问修饰符、getter/setter 实现封装

  • 能够设计安全、可维护、高内聚的类结构

  • 了解封装的优势与潜在代价,避免过度或不足封装


什么是封装

封装(Encapsulation) 是面向对象编程(OOP)的四大基本原则之一。它的核心理念是:

“将数据(属性)和操作数据的方法(行为)捆绑在一个单元(类)中,并对外隐藏内部实现细节。”

Encapsulation.webp

类比理解:胶囊

就像药片把有效成分包裹在胶囊壳内:

  • 你不需要知道药物如何合成

  • 你只需按说明服用(调用公共方法)

  • 制造商可以随时改进配方(内部实现),只要服用方式不变


如何实现封装

Java 通过以下机制实现封装:

步骤

说明

声明字段为 private

阻止外部直接访问或修改数据

提供 public 的 getter/setter

控制对数据的读写,可加入验证逻辑

使用合适的访问修饰符

精细控制类成员的可见性

基础示例:程序员类

class Programmer {
    // 1. 数据私有化
    private String name;

    // 2. 提供受控访问
    public String getName() {
        return name;
    }

    public void setName(String name) {
        // 可在此加入验证逻辑(如非空检查)
        if (name != null && !name.trim().isEmpty()) {
            this.name = name.trim();
        } else {
            throw new IllegalArgumentException("姓名不能为空");
        }
    }
}

public class Main {
    public static void main(String[] args) {
        Programmer p = new Programmer();
        p.setName("张三");
        System.out.println("姓名 => " + p.getName()); // 输出:姓名 => 张三
    }
}

关键点

  • 外部无法直接写 p.name = "李四"(编译错误)

  • 所有修改必须通过 setName(),确保数据合法性


封装的进阶应用

数据验证与业务逻辑

封装不仅是“加 getter/setter”,更是保障对象状态一致性的手段。

示例:银行账户类

class BankAccount {
    private String accountNumber;
    private double balance;

    public BankAccount(String accountNumber, double initialBalance) {
        this.accountNumber = accountNumber;
        setBalance(initialBalance); // 使用 setter 初始化
    }

    public double getBalance() {
        return balance;
    }

    // 不提供 setBalance 公开方法!防止随意修改余额
    private void setBalance(double balance) {
        if (balance < 0) {
            throw new IllegalArgumentException("余额不能为负数");
        }
        this.balance = balance;
    }

    // 提供业务方法:存款
    public void deposit(double amount) {
        if (amount <= 0) {
            throw new IllegalArgumentException("存款金额必须大于 0");
        }
        this.balance += amount;
    }

    // 提供业务方法:取款
    public void withdraw(double amount) {
        if (amount <= 0) {
            throw new IllegalArgumentException("取款金额必须大于 0");
        }
        if (amount > balance) {
            throw new IllegalStateException("余额不足");
        }
        this.balance -= amount;
    }

    public String getAccountNumber() {
        return accountNumber; // 账号只读,不提供 setter
    }
}

安全设计

  • 余额不能被直接设置

  • 所有资金变动必须通过 deposit() / withdraw() 完成

  • 账号一旦创建不可更改(只读)


封装的优势

优势

说明

数据隐藏

防止外部非法访问或篡改内部状态

提升安全性

可在 setter 中加入校验(如邮箱格式、年龄范围)

易于维护

内部实现变更(如存储方式)不影响调用方

增强可读性

类职责清晰,行为集中

保证数据完整性

对象始终处于合法状态(如余额 ≥ 0)

促进代码复用

封装良好的类可作为可靠组件在多项目中使用


封装的潜在缺点

缺点

说明

应对策略

代码量增加

每个字段需写 getter/setter

使用 IDE 自动生成;
或仅对需要控制的字段封装

微小性能开销

方法调用 vs 直接访问

现代 JVM 会内联优化,实际影响极小

过度封装降低灵活性

如完全禁止继承或扩展

合理设计
必要时使用 protected 或提供扩展点

重要提醒不要为了封装而封装!

如果某个字段天然无需验证(如 idcreatedAt),且确定不会变更行为,可考虑简化设计。


封装 vs 其他 OOP 原则

原则

关注点

与封装的关系

抽象

“做什么” vs “怎么做”

封装是实现抽象的手段之一

继承

代码复用

封装保护父类内部细节,子类通过接口交互

多态

同一接口不同实现

封装确保每个实现类内部状态安全


最佳实践

  1. 默认将字段设为 private除非有充分理由(如工具类常量可用 public static final

  2. getter/setter 不是必须的

    • 只读属性:只提供 getter

    • 内部状态:不提供任何访问器

    • 业务行为:用 deposit() 代替 setBalance()

  3. 在 setter 中加入验证逻辑

    public void setAge(int age) {
        if (age < 0 || age > 150) {
            throw new IllegalArgumentException("年龄必须在 0~150 之间");
        }
        this.age = age;
    }
  4. 避免“贫血模型”不要只写 getter/setter 而无业务方法。好的封装类应包含行为,而不仅是数据容器。


重点总结

  • 封装 = 数据私有化 + 受控访问

  • 核心目的:保护对象内部状态,提升系统健壮性

  • 实现方式:private 字段 + public 方法(含验证)

  • 封装不是目的,而是实现高内聚、低耦合设计的手段

  • 合理封装让代码更安全、更易维护、更易测试


思考题

  1. 如果一个类的所有字段都是 public,它是否违反了封装原则?为什么?

  2. 在什么情况下,你可以不为私有字段提供 setter 方法?请举例说明。

  3. 如何在保持封装的同时,允许子类访问父类的某些内部状态?