news 2026/6/25 16:01:21

第10章 封装:让对象保护自己的规则

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
第10章 封装:让对象保护自己的规则

第10章 封装:让对象保护自己的规则

上一章我们用Student类把学号、姓名、分数和是否及格放到了一起。但还有一个严重问题:字段是公开的,外部可以随便改。

Studentstudent=newStudent("S001","Tom",90);student.score=999;student.name="";

这显然不合理。分数不应该超过 100,姓名不应该是空字符串。

封装要解决的问题是:对象内部的数据不能被外部随便破坏,修改数据必须经过对象自己定义的规则。

一、封装不是为了多写 getter/setter

很多人学封装时只记住:

private字段publicgetterpublicsetter

然后机械生成:

privateintscore;publicintgetScore(){returnscore;}publicvoidsetScore(intscore){this.score=score;}

如果 setter 不做任何校验,外部仍然可以:

student.setScore(999);

这只是换了一种方式破坏对象。

真正的封装,是让对象保护自己的规则。

二、private:把字段藏起来

publicclassStudent{privateStringid;privateStringname;privateintscore;}

private表示只能在类内部访问。外部不能:

student.score=90;

这样外部无法绕过对象规则直接修改字段。

三、通过构造方法保证初始状态合法

publicclassStudent{privateStringid;privateStringname;privateintscore;publicStudent(Stringid,Stringname,intscore){if(id==null||id.isEmpty()){thrownewIllegalArgumentException("学号不能为空");}if(name==null||name.isEmpty()){thrownewIllegalArgumentException("姓名不能为空");}if(score<0||score>100){thrownewIllegalArgumentException("分数必须在0到100之间");}this.id=id;this.name=name;this.score=score;}}

这样对象一创建就是合法的。不要创建一个半坏的对象,再希望后面每个地方都小心判断。

四、getter:允许外部读取必要信息

字段 private 后,外部如果需要读取,提供 getter:

publicStringgetName(){returnname;}publicintgetScore(){returnscore;}

读取:

System.out.println(student.getName());

getter 的意义是暴露必要信息。不是所有字段都必须有 getter。只暴露外部确实需要知道的内容。

五、不要无脑 setter,用业务方法

分数可以更新,但要校验:

publicvoidupdateScore(intscore){if(score<0||score>100){thrownewIllegalArgumentException("分数必须在0到100之间");}this.score=score;}

方法名用updateScore,比setScore更有业务含义。

外部调用:

student.updateScore(95);

如果传 999,会抛异常,规则不会被破坏。

六、final:表达创建后不应该改变的字段

学号通常创建后不应改变:

privatefinalStringid;

final 字段必须在构造方法里赋值,赋值后不能再改。

publicStudent(Stringid,Stringname,intscore){this.id=id;this.name=name;this.score=score;}

如果你再写:

this.id="S002";

编译器会阻止。

final 的意义是把“不应该变”的规则交给编译器检查。

七、完整 Student 封装版

publicclassStudent{privatefinalStringid;privateStringname;privateintscore;publicStudent(Stringid,Stringname,intscore){if(id==null||id.isEmpty()){thrownewIllegalArgumentException("学号不能为空");}if(name==null||name.isEmpty()){thrownewIllegalArgumentException("姓名不能为空");}this.id=id;this.name=name;updateScore(score);}publicStringgetId(){returnid;}publicStringgetName(){returnname;}publicintgetScore(){returnscore;}publicvoidrename(StringnewName){if(newName==null||newName.isEmpty()){thrownewIllegalArgumentException("姓名不能为空");}this.name=newName;}publicvoidupdateScore(intscore){if(score<0||score>100){thrownewIllegalArgumentException("分数必须在0到100之间");}this.score=score;}publicbooleanisPassed(){returnscore>=60;}}

这里的设计:

  • id不允许修改,所以 final。
  • name可以改,但必须通过rename
  • score可以改,但必须通过updateScore
  • isPassed是根据 score 计算出来的行为。

八、封装和异常

现在代码里出现了:

thrownewIllegalArgumentException("分数必须在0到100之间");

异常后面会系统讲。这里先理解它的含义:调用方传了非法参数,对象拒绝接受,并抛出错误。

为什么不只是打印?

System.out.println("分数不合法");

如果只是打印,程序可能继续使用一个错误对象。构造方法里发现非法数据时,更合理的是阻止对象创建。

九、封装实战:银行账户

账户不能随便改余额。余额只能通过存款和取款变化。

publicclassBankAccount{privatefinalStringaccountId;privateintbalanceCent;publicBankAccount(StringaccountId,intinitialBalanceCent){if(accountId==null||accountId.isEmpty()){thrownewIllegalArgumentException("账户不能为空");}if(initialBalanceCent<0){thrownewIllegalArgumentException("初始余额不能为负数");}this.accountId=accountId;this.balanceCent=initialBalanceCent;}publicStringgetAccountId(){returnaccountId;}publicintgetBalanceCent(){returnbalanceCent;}publicvoiddeposit(intamountCent){if(amountCent<=0){thrownewIllegalArgumentException("存款金额必须大于0");}balanceCent+=amountCent;}publicvoidwithdraw(intamountCent){if(amountCent<=0){thrownewIllegalArgumentException("取款金额必须大于0");}if(amountCent>balanceCent){thrownewIllegalArgumentException("余额不足");}balanceCent-=amountCent;}}

这个类没有setBalanceCent。因为外部不应该直接设置余额。

这就是封装的真正价值:通过对象方法控制状态变化。

十、封装设计的几个问题

1. 字段是否应该 private?

绝大多数字段应该 private。除非是非常特殊的常量。

2. 是否需要 getter?

外部确实需要读,就提供。否则不要暴露。

3. 是否需要 setter?

先问:这个字段是否允许任意修改?如果不是,就不要机械 setter。

4. 是否应该 final?

创建后不应该变的字段,优先考虑 final。

5. 校验放哪里?

和字段规则强相关的校验,应该放在对象内部。比如分数范围、账户余额不能为负。

十一、常见错误

1. 字段全 public

对象规则完全失控。

2. getter/setter 全生成

看似封装,实际外部仍然随便改。

3. 构造方法不校验

非法对象能被创建,后面所有地方都要补救。

4. 用打印代替错误处理

对象内部发现非法状态,只打印不阻止,风险很大。

5. 方法名没有业务含义

setStatus不如paycancelship清楚。

十二、本章练习

  1. 把上一章的 Book 类改成封装版:
  • id final
  • title 不为空
  • priceCent 不能小于 0
  • 提供changePrice方法
  1. 实现BankAccount,测试存款、取款、余额不足。

  2. 定义Product

  • 商品 id 不可变。
  • 商品名不能为空。
  • 库存不能为负。
  • 提供increaseStockdecreaseStock
  1. 思考:为什么setStock(int stock)不如increaseStock/decreaseStock清楚?

十三、本章总结

封装不是形式,而是规则保护。

你需要掌握:

  • private 隐藏字段。
  • 构造方法保证对象初始状态合法。
  • getter 只暴露必要信息。
  • 不要机械生成 setter。
  • 用业务方法表达状态变化。
  • final 表示创建后不应变化。
  • 对象内部应该维护自己的规则。
  • 非法参数可以用异常阻止。

下一章进入继承和多态。继承能复用代码,但也容易被滥用。我们会先讲它解决什么问题,再讲它的边界。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/25 15:58:56

戴森V6/V7电池开源固件升级完全指南:解锁隐藏的电芯平衡功能

戴森V6/V7电池开源固件升级完全指南&#xff1a;解锁隐藏的电芯平衡功能 【免费下载链接】FU-Dyson-BMS (Unofficial) Firmware Upgrade for Dyson V6/V7 Vacuum Battery Management System 项目地址: https://gitcode.com/gh_mirrors/fu/FU-Dyson-BMS 还在为戴森吸尘器…

作者头像 李华
网站建设 2026/6/25 15:58:23

2026年零基础看量化代码,先用小策略缩小练习范围

零基础学习量化时&#xff0c;最缺的往往不是某个单独知识点&#xff0c;而是一种流程感。读者可能知道要学 Python&#xff0c;也知道量化和交易判断有关&#xff0c;却不清楚一个想法如何变成规则、代码和结果。小策略练习的意义&#xff0c;就在于把这条线缩短到能看见。代码…

作者头像 李华
网站建设 2026/6/25 15:58:15

如何用Flowframes实现专业级AI视频插帧:新手快速上手指南

如何用Flowframes实现专业级AI视频插帧&#xff1a;新手快速上手指南 【免费下载链接】flowframes Flowframes Windows GUI for video interpolation using DAIN (NCNN) or RIFE (CUDA/NCNN) 项目地址: https://gitcode.com/gh_mirrors/fl/flowframes 想要让老旧视频焕发…

作者头像 李华
网站建设 2026/6/25 15:55:00

强化学习Agent为何总在钻空子?奖励函数设计实战指南

1. 这不是科幻&#xff0c;是每天都在发生的现实问题“Can Reinforcement Learning Agents Learn to Game The System?”——这个标题乍看像一篇哲学思辨论文&#xff0c;但如果你在智能调度系统、自动化交易后台、工业质检产线或推荐算法团队里干过三年以上&#xff0c;第一反…

作者头像 李华
网站建设 2026/6/25 15:52:33

云计算作业3

第一题&#xff1a;运行结果&#xff1a;第二题&#xff1a;运行结果&#xff1a;

作者头像 李华