18. 【Java】封装:保护你的数据
摘要本文讲解了Java面向对象编程中的核心概念——封装Encapsulation。通过生动的比喻和丰富的代码示例详细介绍了如何使用访问修饰符private、public等将类的属性隐藏起来并通过公共的getter和setter方法进行安全访问。文章不仅阐述了封装的基本原理和JavaBean规范还探讨了封装与Record类的选择并通过综合实例展示了封装如何提升代码的安全性、可维护性和扩展性。关键词Java封装, 访问修饰符, getter和setter, JavaBean规范, 面向对象编程上篇文章我们理解了“类”和“对象”——学会了如何用设计图类造出具体的实物对象。但我们当时的设计有一个“隐患”所有属性都是直接暴露的。你可以随意访问和修改一个对象的name、age甚至把它改成任何不合理的值。比如说我们定义了一个Student类它有age属性。你完全可以把age设为-5或1500这在语法上是合法的但在现实中是完全不合理的。你会说“谁会把年龄设为负数呢”确实正常情况不会。但错误往往是意外的而不是故意的。一个好的程序应该能够在源头阻止不合理的数据进入系统而不是依赖使用者“自觉”。这就是我们今天要讲的主题 ——封装Encapsulation。1. 封装是什么—— 把“内部”和“外部”隔开封装字面上理解就是“装进一个匣子里封起来”。在面向对象编程中它的含义是把对象的属性和操作属性的方法绑定在一起并对外部隐藏内部实现的细节。说得更直白一点属性数据应该对“外界”不可见不能直接访问只能通过方法来访问和修改。方法的内部实现对外部也是不可见的外部只需要知道“调用这个方法能达到什么效果”而不需要知道“它是怎么做到的”。打个比方你开一台自动洗衣机。你只需要知道“按这个按钮是标准洗按那个按钮是快速洗”你不需要知道洗衣机内部的水泵怎么工作、电机怎么转动、程序怎么控制时间。洗衣机把内部复杂性封装起来了只给你暴露几个简单的按钮即方法。如果洗衣机的电路板直接暴露在外面任你触摸那不仅不安全而且你也无从下手。这就是封装的核心思想简化使用保护安全。2. 访问修饰符 —— 控制“可见性”的大门Java 通过访问修饰符Access Modifiers来实现封装。它们决定了“谁能访问这个成员字段或方法”。修饰符同一个类同一个包不同包的子类任何地方private✅❌❌❌default不写✅✅❌❌protected✅✅✅❌public✅✅✅✅我们一个一个来看。private—— 最严格的“隐私保护”用private修饰的成员只能在当前类内部访问。外部任何地方都不能直接访问它。这是我们实现封装最常用的修饰符。通常我们把所有属性都设为private然后通过public的方法来间接访问它们。publicclassStudent{privateStringname;// 私有属性外部不能直接访问privateintage;// 通过公共方法访问publicStringgetName(){returnname;}publicvoidsetName(Stringname){this.namename;}}public—— 完全公开public修饰的成员任何地方都能访问。我们通常把方法设为public作为对外提供的“服务入口”。default包级私有 —— 同一包内的“朋友”如果不写任何修饰符就是default也叫“包级私有”。它表示只有同一个包package内的类可以访问。这个用得相对较少。protected—— 留给“子类”的权限protected表示“同一个包内 不同包的子类”可以访问。它主要用在继承关系中我们后面会详细讲。3. 实践给Student加上封装让我们把之前那个完全暴露的Student类改造一下加上封装publicclassStudent{// 所有属性都设为 privateprivateStringname;privateintage;privateStringstudentId;// ---- 构造方法 ----publicStudent(Stringname,intage,StringstudentId){// 在构造方法中我们也可以使用 setter 来做校验setName(name);setAge(age);setStudentId(studentId);}// ---- Getter 方法 ----publicStringgetName(){returnname;}publicintgetAge(){returnage;}publicStringgetStudentId(){returnstudentId;}// ---- Setter 方法 ----publicvoidsetName(Stringname){// 可以加一些基本校验if(namenull||name.trim().isEmpty()){System.out.println(警告姓名不能为空);this.name未知;}else{this.namename;}}publicvoidsetAge(intage){// 校验年龄的合理性if(age0||age150){System.out.println(警告年龄 age 不合理已设为 0。);this.age0;}else{this.ageage;}}publicvoidsetStudentId(StringstudentId){if(studentIdnull||studentId.trim().isEmpty()){System.out.println(警告学号不能为空);this.studentId未知;}else{this.studentIdstudentId;}}// ---- 业务方法 ----publicvoidsayHello(){System.out.println(你好我是 name今年 age 岁学号 studentId);}}现在外部代码不能再随意赋值了publicclassMain{publicstaticvoidmain(String[]args){Students1newStudent(张三,20,2024001);s1.sayHello();// 输出你好我是 张三今年 20 岁学号 2024001// 尝试设置不合理的数据s1.setAge(-5);// 会输出警告并把 age 设为 0s1.setName();// 会输出警告并把 name 设为 未知s1.sayHello();// 输出你好我是 未知今年 0 岁学号 2024001}}你看封装让我们的类变得“有脑子”了——它能够主动保护自己的数据拒绝不合理的信息而不是傻傻地接受任何值。4. 封装不仅仅是“私有化” —— 更高层的含义① 把变化隔离如果某个属性的存储方式发生变化比如把age从int改为String存储只要getAge()和setAge()的方法签名不变外部调用者完全不需要修改任何代码。变化被隔离在了类内部。② 提供“只读”或“只写”的权限有时候你希望某个属性只能读不能改。那你只需要提供getter不提供setter。privateStringid;// ID 一旦生成就不能修改publicStringgetId(){returnid;}// 没有 setter外部无法修改 id③ 在方法内部做额外的逻辑除了参数校验你还可以在 getter/setter 中做更多的事情比如记录日志谁在什么时候读取或修改了某个属性触发其他事件修改了价格自动重新计算总价延迟加载从数据库读取5. JavaBean 规范 —— 一种“约定俗成”的写法在 Java 开发中尤其是 Spring 等框架中你经常会听到JavaBean这个词。它不是一个类也不是一种技术而是一套编码规范类是public的。所有属性都是private的。每个属性都有对应的publicgetter 和 setter 方法除非该属性是只读的。有一个无参构造方法即使有有参构造也建议保留无参构造。实现Serializable接口可选用于序列化。我们平时的 POJOPlain Old Java Object普通 Java 对象类就是按照这套规范写的。publicclassUserimplementsSerializable{privateStringusername;privateStringpassword;privateintage;publicUser(){}// 无参构造publicUser(Stringusername,Stringpassword,intage){this.usernameusername;this.passwordpassword;this.ageage;}publicStringgetUsername(){returnusername;}publicvoidsetUsername(Stringusername){this.usernameusername;}publicStringgetPassword(){returnpassword;}publicvoidsetPassword(Stringpassword){this.passwordpassword;}publicintgetAge(){returnage;}publicvoidsetAge(intage){this.ageage;}}这套规范的优点是什么框架能够通过反射机制识别你的类结构自动完成很多工作比如序列化、数据绑定、JSON 转换等。如果你不遵守这个规范很多框架功能就无法生效。6. 封装与Record—— 新方式与旧方式的选择你可能会想“我们现在写的 getter/setter 好啰嗦啊那上一篇学的Record不香吗”确实Record自动生成了构造方法、访问器name()、age()和toString()但它不生成 setter因为 Record 是不可变的。两者各有适用场景场景推荐数据只读不需要修改✅Record简洁优雅数据需要修改有业务逻辑校验✅ 普通类 封装private getter/setter需要兼容框架如 Spring普通类更常见但 Record 也逐渐被支持在初学阶段我建议你把普通类的封装写法练熟因为它在实际工作中仍然是主流。等你熟练之后再根据场景灵活选择 Record。7. 一个综合例子带封装的商品类我们来写一个稍微复杂的例子——一个Product商品类包含封装、构造方法、业务方法publicclassProduct{// ---- 私有属性 ----privateStringid;privateStringname;privatedoubleprice;privateintstock;// 库存数量// ---- 构造方法 ----publicProduct(Stringid,Stringname,doubleprice,intstock){this.idid;setName(name);setPrice(price);setStock(stock);}// ---- Getter 方法 ----publicStringgetId(){returnid;}publicStringgetName(){returnname;}publicdoublegetPrice(){returnprice;}publicintgetStock(){returnstock;}// ---- Setter 方法带校验 ----publicvoidsetName(Stringname){if(namenull||name.trim().isEmpty()){thrownewIllegalArgumentException(商品名称不能为空);}this.namename;}publicvoidsetPrice(doubleprice){if(price0){thrownewIllegalArgumentException(商品价格不能为负数);}this.priceprice;}publicvoidsetStock(intstock){if(stock0){thrownewIllegalArgumentException(库存数量不能为负数);}this.stockstock;}// ---- 业务方法 ----/** * 减少库存发货时调用 */publicvoidreduceStock(intquantity){if(quantity0){System.out.println(❌ 减少数量必须大于 0);return;}if(stockquantity){System.out.println(❌ 库存不足当前库存stock需要quantity);return;}stock-quantity;System.out.println(✅ 库存已减少 quantity剩余库存stock);}/** * 增加库存补货时调用 */publicvoidincreaseStock(intquantity){if(quantity0){System.out.println(❌ 增加数量必须大于 0);return;}stockquantity;System.out.println(✅ 库存已增加 quantity当前库存stock);}/** * 计算折扣价打 8 折 */publicdoublegetDiscountedPrice(doublediscountRate){if(discountRate0||discountRate1){System.out.println(❌ 折扣率必须在 0~1 之间);returnprice;}returnprice*(1-discountRate);}/** * 显示商品信息 */publicvoiddisplay(){System.out.println(商品IDid);System.out.println(商品名称name);System.out.println(商品价格price);System.out.println(库存数量stock);}}publicclassMain{publicstaticvoidmain(String[]args){ProductbooknewProduct(P001,Java编程思想,99.0,50);book.display();System.out.println(-------------------);// 尝试打折doublediscountedbook.getDiscountedPrice(0.2);System.out.println(8折后价格discounted);// 尝试减少库存book.reduceStock(10);// 库存减少 10book.reduceStock(100);// 库存不足报错// 尝试设置不合理的数据会抛异常try{book.setPrice(-10);}catch(IllegalArgumentExceptione){System.out.println(捕获异常e.getMessage());}}}这个例子展示了封装如何让一个类变得安全数据校验和自包含数据 操作数据的方法放在一起。8. 封装与面向对象的关系封装是面向对象三大特性中的第一个另外两个是继承和多态。它奠定了面向对象的基础没有了封装对象的属性可以被随意篡改数据完整性无法保证。没有了封装对象的内部实现对外部透明一旦实现改变所有依赖的代码都要修改。没有了封装对象就只是一个“数据袋子”而不是一个有“行为”和“职责”的实体。可以说封装是面向对象的“第一块砖”。今天的总结今天我们彻底理解了封装封装把数据和操作数据的方法绑在一起对外部隐藏内部细节。访问修饰符private最严格、default包级、protected继承相关、public完全公开。通常的做法属性用private方法用public。Getter/Setter 不只是“读取/设置”它们提供了校验、隔离变化、控制权限的机会。JavaBean 规范private 属性 public getter/setter 无参构造。封装让代码更安全、更可维护、更易扩展。现在你写的类已经有模有样了。它不再是一堆数据的堆砌而是一个能自我保护的“小实体”。动手试试把上篇文章的Book类改造成符合封装规范的版本所有属性private提供 getter/setter在 setter 中加入合理的校验价格不能为负、书名不能为空等。设计一个BankAccount银行账户类包含accountNumber、balance属性。提供deposit(double amount)和withdraw(double amount)方法确保余额不能为负取款不能超出余额。写一个Temperature类内部用double celsius存储温度。提供getCelsius()、setCelsius()、getFahrenheit()、setFahrenheit()方法实现摄氏度和华氏度的自动转换。公式F C * 9/5 32挑战结合第 6 篇的“名片生成器”和第 17 篇的Person类把Person类的属性改为private在 setter 中加入校验年龄 0~150姓名非空并重构printCard()方法直接使用私有属性。多写几个封装的类你会越来越习惯“把数据藏起来”的思维方式。这种“藏”不是不让人用而是让人通过正规的、受控的途径去用。我们下一篇见。 获取本系列示例代码请访问 GitCode。

相关新闻

微信聊天记录解密终极指南:3分钟掌握数据恢复核心技术

微信聊天记录解密终极指南:3分钟掌握数据恢复核心技术

微信聊天记录解密终极指南:3分钟掌握数据恢复核心技术 【免费下载链接】WechatDecrypt 微信消息解密工具 项目地址: https://gitcode.com/gh_mirrors/we/WechatDecrypt 微信作为国内最主流的即时通讯工具,每天承载着数十亿条聊天记录。然而&#…

2026/10/11 18:16:37 阅读更多 →
17. 【Java】类(抽象)与对象:万物皆对象

17. 【Java】类(抽象)与对象:万物皆对象

摘要: 本文是 Java 面向对象编程的入门指南,旨在帮助初学者理解类与对象的核心概念。文章通过生动的比喻(如“图纸”与“实物”)阐释了类作为蓝图、对象作为实例的关系,详细讲解了如何定义类、创建对象、使用构造方法以…

2026/10/11 6:46:22 阅读更多 →
构建高效离线漫画库:Aidoku下载功能深度解析

构建高效离线漫画库:Aidoku下载功能深度解析

构建高效离线漫画库:Aidoku下载功能深度解析 【免费下载链接】Aidoku Free and open source manga reader for iOS and iPadOS 项目地址: https://gitcode.com/gh_mirrors/ai/Aidoku 你是否曾经在地铁上、飞机上或者网络信号不佳的地方想要阅读漫画却束手无策…

2026/9/30 3:08:49 阅读更多 →

最新新闻

Multi-Agent系统Tracing实战:从线上事故到执行链记录体系

Multi-Agent系统Tracing实战:从线上事故到执行链记录体系

1. 从一次线上事故说起:为什么Multi-Agent系统必须做Tracing去年冬天,我们团队上线了一套基于多智能体协作的自动化内容处理系统,四个Agent分别负责信息抽取、逻辑校验、格式转换和最终输出。上线第三天,运营同学反馈说有一批文档…

2026/10/15 5:38:22 阅读更多 →
Java批量生成资产标签PDF:itextpdf配ZXing二维码实战指南

Java批量生成资产标签PDF:itextpdf配ZXing二维码实战指南

上周有位朋友微信找我,说单位三百多台仪器,每年盘点都要两个人蹲在地上逐个抄标签,标签还是买设备时经销商送的,掉了三年没人补。他问我:网上说的“用itextpdf 配合 google.zxing 批量生成资产标签PDF”,到…

2026/10/15 5:38:22 阅读更多 →
Java面试八股文核心真题:并发、JVM、MySQL与Redis深度解析

Java面试八股文核心真题:并发、JVM、MySQL与Redis深度解析

每次面试前,总有人问我Java“八股文”到底要不要背。我的态度很明确:不仅要背,还得背出深度。你光知道synchronized是锁,面试官下一句就是“锁升级过程讲一下”;你光知道HashMap底层是数组加链表,紧接着就是…

2026/10/15 5:38:22 阅读更多 →
Agent Harness工程实战:从循环、上下文到沙箱与验证的稳定运行设计

Agent Harness工程实战:从循环、上下文到沙箱与验证的稳定运行设计

1. 从"能跑"到"跑得住":Harness 工程到底在解决什么很多人第一次接触 Agent 开发,注意力几乎全放在模型本身——提示词怎么写、工具怎么调、输出怎么解析。等真正把 Agent 丢进一个需要连续跑几十分钟甚至几小时的任务里&#xff0c…

2026/10/15 5:38:22 阅读更多 →
用华为云码道 CodeArts 代码智能体,三天从零做出一个全栈 AI 求职平台(附源码)

用华为云码道 CodeArts 代码智能体,三天从零做出一个全栈 AI 求职平台(附源码)

一键开通华为云码道 CodeArts 代码智能体 项目仓库:https://atomgit.com/gcw_93rlw6ed/careerpilot (公开,MIT 协议) 演示账号:打开登录页自动进入(演示账号 candidatedemo.com 自动登录) 一、…

2026/10/15 5:38:22 阅读更多 →
Vue项目实现Office文档在线预览:DOCX、XLSX、PPT一次搞定

Vue项目实现Office文档在线预览:DOCX、XLSX、PPT一次搞定

做前端这几年,被文档预览折磨的次数真不算少。之前接一个合同管理需求,用户提了个特别朴素的要求:附件里的Word合同不要下载到本地再打开,在网页里直接能看就行。我一开始也心想,这有什么难的?不就是嵌一个…

2026/10/15 5:37:22 阅读更多 →

日新闻

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

1. 为什么选择容器化方式部署 LoRa 网络服务LoRa 物联网项目落地时,网络服务器是整个链路的核心枢纽。它负责接收网关上报的数据包、完成去重与解密、再把有效载荷分发给上层应用。早期大家习惯把网络服务器直接装在物理机上,依赖一堆系统库和特定版本的…

2026/10/15 0:00:46 阅读更多 →
共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

1. 共享推理服务里,扩缩容到底难在哪做过大模型推理服务部署的人,大概都经历过这样的场景:一个团队共用一套推理集群,A 业务跑的是 7B 的小模型,B 业务跑的是 72B 的大模型,C 业务白天流量高、晚上几乎归零…

2026/10/15 0:00:46 阅读更多 →
Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

1. 引子:一次内核恐慌引发的追查先从一个真实场景说起。某次我给一台老旧的嵌入式设备折腾系统裁减,为了把启动时间压进两秒以内,我手贱地把一串自认为“没用”的 systemd 服务给 disable 了,其中就包括systemd-remount-fs.servic…

2026/10/15 0:00:46 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/14 16:30:29 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 0:14:36 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 7:53:34 阅读更多 →