CodeGym /课程 /JAVA 25 SELF /封装的原则:为什么需要它

封装的原则:为什么需要它

JAVA 25 SELF
第 15 级 , 课程 0
可用

1. 封装的定义

封装 是面向对象编程(OOP)的基础原则之一。通俗地说,封装就是把对象的内部细节(它的“内脏”)藏起来,只通过专门设计好的“门”——公共方法——来访问。

想象一台现代咖啡机。用户只看到按钮和显示屏——他不需要知道锅炉、泵和内部管路如何工作。他按下“卡布奇诺”——就得到结果。内部的一切都被隐藏了。这就是封装!

Java(以及其他 OOP 语言)中,封装通过以下方式实现:

  • 数据隐藏——将类的字段声明为 private(至少不要是 public)。
  • 公共接口——只“暴露”那些对象用户真正需要的方法。

示意图:封装长什么样

+-------------------------------+
|          Class Student        |
|-------------------------------|
| - name: String                |  // private 字段
| - age: int                    |  // private 字段
|-------------------------------|
| + getName(): String           |  // public 方法
| + setName(String): void       |  // public 方法
| + getAge(): int               |  // public 方法
| + setAge(int): void           |  // public 方法
+-------------------------------+

这里的 - 表示 private(隐藏),而 + 表示 public(对外可见)。

什么是 getter 和 setter?

在讨论为什么需要封装之前,先快速认识一下 gettersetter——它们是与类的私有字段“交流”的专用方法。

Getter——获取私有字段值的方法。通常命名为 getFieldName()

Setter——设置私有字段值的方法。通常命名为 setFieldName(value)

简单示例:

public class Student {
    private String name; // 私有字段——外部不可见
    
    // Getter —— “把学生的名字给我”
    public String getName() {
        return name;
    }
    
    // Setter —— “设置学生的名字”
    public void setName(String name) {
        this.name = name;
    }
}

如何使用:

Student student = new Student();
student.setName("Vasya");           // 通过 setter 设置名字
String name = student.getName();   // 通过 getter 获取名字

把 getter 和 setter 想象成向对象提出的“礼貌请求”:与其直接往它口袋里掏东西(student.name = "Vasya"),不如有礼貌地请求:“请设置名字”(student.setName("Vasya"))。

提前剧透:再过几讲我们会系统学习 getter 和 setter,了解它们的技巧并熟练运用。现在只需理解核心思想即可!

2. 为什么需要封装?

保护数据不被不当使用

如果类的所有字段都是 public,任何外部代码都能随意修改它们:

Student s = new Student();
s.age = -1000; // 哎呀,吸血鬼学生!

这很危险!程序可能表现出不可预测的行为,bug 会在最意想不到的地方冒出来。

在不影响外部代码的情况下修改内部实现

封装允许你修改类的内部实现,而不破坏它的使用方代码。例如,你可以改变数据的存储方式,或在方法中加入校验,类的使用者不会察觉——他们仍然调用相同的方法。

提升代码的可读性与可维护性

当内部细节被隐藏后,对外接口会更简洁、更清晰。使用你类的程序员无需理解内部工作原理——只需知道有哪些方法可用以及它们的作用。

生活中的例子

回想你如何使用智能手机。你不会去想屏幕触摸如何被处理、电池如何构造、通信模块如何工作。你只通过清晰的接口(图标、按钮)调用所需功能。哪怕厂商更换了内部实现,你也不会察觉。

代码中的真实示例

假设我们有一个类 BankAccount。旧版本程序把余额存成用点分隔的字符串,比如 "1.000.50"。后来程序员决定把余额存成数字 double。如果这个字段是公开的,那么所有直接访问 account.balance 的旧代码都会崩掉。

但如果我们使用封装,隐藏字段,只提供 deposit()getBalance() 方法,外部代码甚至不会知道发生了变更:

public class BankAccount {
    private double balance; // 字段被隐藏

    public void deposit(double amount) {
        if (amount > 0) {
            balance += amount;
        }
    }

    public double getBalance() {
        return balance;
    }
}

现在如果明天我们想把余额按“分”来存(例如用 long),只需修改类的内部实现,所有调用 deposit()getBalance() 的其他代码都会像以前一样正常工作。

3. 封装的反例与正例

反例:公开字段

public class Student {
    public String name;
    public int age;
}

这种做法的问题:

  • 任何代码都能给字段赋任意值,甚至是无效值。
  • 无法加入数据校验。
  • 如果你决定修改字段的类型或结构,所有使用它的代码都得改。

正例:私有字段与公共方法

public class Student {
    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        // 可以添加校验!
        if (name == null || name.isEmpty()) {
            throw new IllegalArgumentException("名字不能为空");
        }
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        // 确保年龄非负
        if (age < 0) {
            throw new IllegalArgumentException("年龄不能为负数");
        }
        this.age = age;
    }
}

优点:

  • 外部代码不能直接修改字段——只能通过方法。
  • 可以添加校验、日志、自动行为(例如更新统计信息)。
  • 即使内部表示发生变化(例如用另一种格式存年龄),对外接口依然不变。

使用时的样子

Student s = new Student();
s.setName("Alisa");
s.setAge(20);

System.out.println(s.getName() + ",年龄:" + s.getAge());

试着把年龄设为负数——你会在运行期立刻得到错误!你的程序能避免低级失误。

4. 与其他 OOP 原则的关系

封装是其他 OOP 原则的“母体”。没有它,就不会有继承、多态和抽象。我们稍后会详细学习它们,这里先简单提一下:

  • 继承extends)允许基于已有类创建新类,扩展或修改其行为。如果一个类的内部细节是开放的,子类可能会不小心破坏关键部分。
  • 多态(不同类的对象对同一消息作出不同响应的能力)离不开对内部实现与外部接口的清晰分离。
  • 抽象——只保留对象的重要特征并隐藏细节。封装有助于把抽象落地实现。

类比

想象一辆汽车。驾驶者只接触方向盘、踏板和手柄——这是接口。其他一切(发动机、变速箱、电子设备)都隐藏在引擎盖之下。如果驾驶者能直接操纵发动机的每个螺丝,事故会频繁得多!

5. 实战示例:在我们的应用中使用封装

让我们继续扩展教学应用——比如“通讯录”。假设有一个 Contact 类,存储姓名和电话。

没有封装(反例):

public class Contact {
    public String name;
    public String phone;
}

使用方式:

Contact contact = new Contact();
contact.name = ""; // 哎呀!名字是空的
contact.phone = null; // 电话未设置

有封装(正确做法):

public class Contact {
    private String name;
    private String phone;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        if (name == null || name.isBlank()) {
            throw new IllegalArgumentException("联系人姓名不能为空");
        }
        this.name = name;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        if (phone == null || phone.isBlank()) {
            throw new IllegalArgumentException("电话不能为空");
        }
        this.phone = phone;
    }
}

现在外部代码不能留下空名字或空电话:

Contact contact = new Contact();
contact.setName("伊万");
contact.setPhone("+1-999-123-45-67");

如果试图设置空名字——程序会抛出错误。

6. 有用的细节

封装与长期维护

在小型教学项目里,你可能觉得“凭良心写代码”也行:谁会把年龄设为负数或名字设为空呢?但一旦项目变大、有了其他开发者,甚至你自己过几个月也会忘了实现细节——这时封装就能拯救混乱。

  • 更容易修改类的内部——如果需要把电话存为 PhoneNumber 对象而不是字符串,你只需修改实现,外部代码无需改动。
  • 更易测试——如果所有修改都通过方法进行,就容易追踪何时修改了哪些数据。
  • 更少 bug——防止无效值和意外改动。

问题:是否总是需要 getter 和 setter?

很多初学者以为:“既然封装就是私有字段配公共 getter/setter,那每个字段都要配 getter 和 setter!”并不总是如此。

  • 有时字段应当只读(例如对象的唯一标识)。那就只提供 getter。
  • 有的字段根本不需要“对外暴露”——那就不要提供 getter 或 setter。
  • 如果字段只能在类内部修改,可以把 setter 设为私有的。

黄金法则:只开放外部代码真正需要的数据和方法。

可视化:方法对比

方式 字段访问示例 可控性 安全性
public 字段
obj.field = value;
private 字段 + 方法
obj.setField(value);

7. 使用封装时的常见错误

错误 № 1:把类的所有字段都声明为 public。 这是初学者最常见的错误。这类代码很快会失控:任何人都能在你不知情的情况下修改任何数据。不要这么做——哪怕你很想省时间!

错误 № 2:没有校验与逻辑的 getter 和 setter。 既然你提供了访问方法,就利用它们做校验:不要允许赋予无效值。简单地把参数值“原样”拷贝到字段里并不总是最佳做法。

错误 № 3:过早暴露内部结构。 如果你“以防万一”给所有字段都提前做了 getter/setter,你就有可能暴露过多细节,后续会很难修改。

错误 № 4:直接返回可变对象。 如果字段是可变对象(比如列表),不要通过 getter 直接返回它。最好返回副本,或让其不可变。

1
任务
JAVA 25 SELF, 第 15 级, 课程 0
已锁定
俱乐部会员的机密信息 🕵️‍♀️
俱乐部会员的机密信息 🕵️‍♀️
1
任务
JAVA 25 SELF, 第 15 级, 课程 0
已锁定
介绍俱乐部成员 🤝
介绍俱乐部成员 🤝
1
任务
JAVA 25 SELF, 第 15 级, 课程 0
已锁定
数据校验与错误保护 🚫
数据校验与错误保护 🚫
1
任务
JAVA 25 SELF, 第 15 级, 课程 0
已锁定
仓库中不可更改的商品标签 🏷️
仓库中不可更改的商品标签 🏷️
评论 (1)
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION
级别 18,-,Hong Kong
8 八月 2026
???俺的鸡汤呢🤔