封装是什么?
在面向对象编程中,封装(encapsulation)是指把对象的数据和操作这些数据的方法放在同一个单元(通常是一个类)里,并隐藏内部细节,让其他代码只能通过对象的公有方法来使用它。
更新于 2026年9月24日
假设一个银行应用把每个账户的余额存放在一个普通变量里。程序中任何地方的任何一行代码都可以写 account.balance = -500,没有什么能阻止它。当生产环境中出现负余额时,你只能在整个代码库里找出每一个碰过它的地方。封装解决了这个问题:余额只能通过少数几个方法来修改,而这些方法会强制执行规则,比如取款金额永远不能超过余额。
封装是如何工作的
封装结合了两个思想:
- 捆绑。 数据(余额)和操作数据的代码(存款、取款)放在同一个类里。
- 隐藏。 其他代码只能看到类的公有接口。接口背后的数据属于内部实现,只能以类允许的方式修改。
类所保护的规则叫作不变式(invariant),这里就是“余额永远不为负”。由于每一次修改都要经过类的方法,无论程序的其他部分做什么,这个不变式都始终成立。
70
refused: not enough money
外部代码可以读取 acct.balance,但这个属性没有 setter,所以 acct.balance = 1000 会抛出 AttributeError。如果余额出了错,需要检查的方法只有两个。
Python 中的封装
Python 没有 private 关键字。它依靠的是每个 Python 程序员都熟悉的命名约定:
name:公有,属于接口的一部分。_name:内部使用。没有什么能阻止你读取它,但开头的下划线在告诉你“不要依赖它”。__name:Python 会在类内部给它改名(名称改写),这样子类就不会意外覆盖它。@property:把一个方法变成读起来像属性的东西,这样你以后可以加入检查,而无需修改任何调用代码。
0
False
42
名称改写防的是意外,而不是访问:改写后的名字照样能用。Python 相信程序员会尊重下划线的约定。实际上,大多数 Python 代码使用单下划线加 @property,只在专门用于继承的类中才使用双下划线。关于如何编写类,可以参阅 Python 类文档。
Java、C++ 和 JavaScript 中的封装
其他语言会强制执行隐藏。在 Java 中,private 字段只能在它所属的类内部使用:
public class BankAccount {
private int balance; // hidden from every other class
public int getBalance() { return balance; }
public void withdraw(int amount) {
if (amount > balance) throw new IllegalArgumentException("not enough money");
balance -= amount;
}
}
在另一个类中写 account.balance = 1000; 无法通过编译:javac 会报告 balance has private access in BankAccount。
| 语言 | 隐藏的成员 | 公有的成员 | 由谁强制执行 |
|---|---|---|---|
| Python | _name、__name | name | 约定 |
| Java | private int name | public int name | 编译器 |
| C++ | private: 部分 | public: 部分 | 编译器 |
| JavaScript | #name | name | JavaScript 引擎 |
C++ 中 class 的成员默认是私有的,struct 的成员默认是公有的。JavaScript 在 ES2022 中加入的 # 私有字段,在类外部完全无法读取。文档对这几种都有讲解:Java 访问修饰符、C++ 访问说明符和 JavaScript 私有字段。
封装为什么重要
- 规则集中在一处。 “取款不能超过余额”这条检查只在
withdraw里写一次,而不是在程序中每次取款之前都写一遍。 - 内部可以随意修改。 如果你决定把金额从浮点数改为以“分”为单位的整数存储,只需修改这个类。调用
deposit(100)的代码照常工作。 - 调试更容易。 当某个值出错时,嫌疑对象只是少数几个有权修改它的方法,而不是程序中的每一行代码。
- 需要学习和测试的东西更少。 一个有三个公有方法的类,通过这三个方法就能理解和测试。
封装也适用于比类更大的层面。一个只公开两个函数、把十个辅助函数留在内部的模块,也是同样意义上的封装;只允许程序通过系统调用来打开文件的操作系统也是如此。
封装经常和抽象混为一谈。抽象决定接口提供什么;封装保护接口背后的数据。抽象页面有一张完整的对比表。它们与多态和继承一起,构成了面向对象编程的四大特性。
常见错误
- 给每个字段都配上 getter 和 setter。 一个接受任意数字的
setBalance什么也保护不了。应该提供对象真正需要的操作(withdraw),而不是对每个字段的直接访问。 - 返回内部对象。 一个返回
self._items的方法,等于把真正的列表交给了调用方,调用方现在可以随意修改它。应该返回一个副本或元组。 - 把私有当作安全手段。 访问修饰符防的是失误,不是攻击。Java 代码可以通过反射读取私有字段,Python 代码可以直接读取
_name。密码和密钥需要真正的保护措施,比如哈希和加密。
下一步学什么
接下来可以阅读抽象,了解这两个概念的区别,然后阅读多态。Python 课程会一步步带你编写类,Java 课程则展示了由编译器强制执行的封装。
常见问题
用简单的话说,封装是什么?
deposit 和 withdraw,它们会先检查规则。这个名字来自胶囊(capsule),也就是把内容物包在里面的外壳。封装有哪些例子?
deposit 和 withdraw 修改余额的银行账户类是最经典的例子。Python 自带的 list 也是一个例子:你调用 append,却从来不用管理存放元素的内存。一个只公开几个函数、把辅助函数留作私有的模块,则是更大范围的封装。封装有哪三种类型?
列出三种类型的教程通常指的是:成员变量封装(私有字段)、函数封装(私有辅助方法)和类封装(隐藏在另一个类或模块内部的类)。这些并不是正式的分类,只是把同一个思想分别应用到字段、方法和整个类上。
C++ 中的封装是什么,怎么用?
private,再提供 public 成员函数来读取或修改它们。class 的成员默认是私有的,而 struct 的成员默认是公有的。类外部任何试图使用私有成员的代码都会被编译器拒绝。网络中的封装是什么意思?
在计算机网络中,封装是指数据在发出的过程中被逐层包上各个协议层的头部:HTTP 消息放进 TCP 段,TCP 段放进 IP 数据包,IP 数据包再放进以太网帧。它和编程中的封装同名,但是两个不同的概念。