Day14 --【Java全栈】 面向对象编程[2]封装 面向对象三大核心特性封装、继承、多态是面向对象的三大支柱也是面试 100% 会问到的核心。思考如果类里的属性、方法全都对外公开所有人想改就直接改会出现什么问题全部公开 数据没人管、逻辑随便破、改一动全身、出 bug 查不到、想优化无从下手。那么问题怎么解决呢这个时候就需要用到封装封装Encapsulation一、 定义封装是面向对象的核心思想之一将对象的内部状态属性和内部实现方法隐藏起来对外只暴露有限的、可控的访问入口所有对内部数据的修改都必须通过指定入口完成。核心本质不是「完全不让访问」而是「可控访问」——把数据和逻辑的控制权牢牢握在类自己手里外部只能通过我们允许的方式操作。 通俗理解如果奶茶店没有封装操作间全透明顾客可以自己伸手进机器改糖度、自己拿珍珠、自己改价格会发生什么有人乱改配方做出来的奶茶口味全乱了有人直接改价格12块的奶茶改成1块钱店铺亏钱制作步骤随便改今天煮茶3分钟、明天煮10分钟品质完全不稳定。封装就是给操作间装上玻璃墙、加上门禁顾客看不到里面的配料比例、制作步骤也碰不到原料想喝奶茶只能通过「前台点单」这个唯一正规入口说清楚口味、甜度内部怎么制作、用什么配方、加多少料全由奶茶店自己把控顾客不用管也改不了。二、封装的三大核心价值1. 保护数据安全杜绝非法值比如奶茶价格不能为负数、甜度只能选3/5/7/10分、库存不能小于0。如果属性直接对外暴露外部随便赋值很容易产生脏数据通过方法入口就能加校验保证数据永远合法。更深一层封装还能保证对象内部多个属性之间的状态一致性。比如「订单状态」和「支付时间」必须同步变化状态变为已支付时支付时间也要同步赋值。如果两个属性直接对外暴露就可能出现只改了状态、忘了更新时间的矛盾脏数据。封装可以通过一个统一的行为方法一次性修改所有相关属性从根源上杜绝这种状态不一致的问题。2. 降低代码耦合内部修改不影响外部只要对外的入口方法名、参数不变内部怎么改逻辑、怎么优化实现外部调用者完全感知不到也不用改代码。比如奶茶制作步骤升级了顾客还是只要说「做一杯珍珠奶茶」就行不用关心内部流程变了。3. 简化使用成本隐藏实现复杂度调用者不用知道内部有多少步骤、多少细节只要调用对外的方法就能完成功能。比如你不用知道奶茶怎么做只要点单就能拿到成品。三、Java 实现封装的基石访问控制修饰符既然封装的核心是「可控访问」那Java靠什么来控制不同范围的访问权限呢答案就是四类访问控制修饰符。封装的底层就是靠这四类访问权限来控制「谁能访问、谁不能访问」。这四个修饰符可以修饰成员变量、成员方法、类控制可见范围。权限从小到大对比⬇️修饰符同类内同包下不同包的子类所有地方奶茶店类比核心使用场景private私有✅ 可访问❌ 不可访问❌ 不可访问❌ 不可访问操作间里的原料配比、秘制配方只有奶茶师自己能碰成员变量、内部工具方法彻底隐藏内部细节default包私有不写就是它✅ 可访问✅ 可访问❌ 不可访问❌ 不可访问同一家门店的所有员工都能进的后厨区外店人进不来同包内复用的工具类、辅助方法protected受保护✅ 可访问✅ 可访问✅ 可访问❌ 不可访问总店配方所有分店的奶茶师都能用外部顾客不行专门留给子类重写、复用的方法public公共✅ 可访问✅ 可访问✅ 可访问✅ 可访问前台点单窗口所有人都能来下单对外暴露的正式方法、类的对外接口 记忆default 和 protected 的唯一区别就体现在不同包的子类场景——protected 允许子类访问default 不允许。我们平时说的「属性私有化、方法公开化」就是用private修饰属性用public修饰对外的访问方法这是封装最经典、最常用的实现方式。详细可见➡️Day10 --【Java全栈】方法 (上)代码示例不同权限在奶茶类里的体现⬇️public class MilkTea { // 私有属性private彻底隐藏外部不能直接碰 private String taste; // 口味 private double price; // 价格 private String secretFormula; // 秘制配方只有类内部能访问 // 受保护方法protected同包子类能用给子类复用 protected void boilTea() { System.out.println(煮茶底按照 secretFormula 配方煮); } // 包私有方法default不写修饰符同包下能用 void addMilk() { System.out.println(加牛奶); } // 公共方法public对外暴露的唯一入口 // 获取价格 public double getPrice() { return price; } // 设置价格内置校验 public void setPrice(double price) { if(price 0) { System.out.println(价格不能为负数设置失败); return; } this.price price; } // 对外的制作方法内部调用私有步骤 public void make() { boilTea(); addMilk(); System.out.println(制作完成 taste 奶茶); } }四、this关键词1.定义this是 Java 中的关键字代表当前对象的引用也就是指向当前正在执行的对象本身。它可以用来访问当前对象的属性、方法也可以调用当前对象的其他构造方法。顺着封装的场景往下想我们写 set 方法时为了见名知意通常会把参数名和成员变量名写得一致比如都叫price那程序怎么区分哪个是类的成员属性、哪个是方法的局部参数呢 通俗理解this就代表「当前正在操作的这个对象自己」。类比奶茶店奶茶师手里正在制作的这杯奶茶就是this。当他说「我的口味」「我的价格」时指的就是当前这杯奶茶自己的属性而不是别的奶茶、也不是原料。一句话哪个对象调用了方法方法里的 this 就代表哪个对象。2. 封装场景下this的三大核心用法用法1区分成员变量和局部变量封装最核心、最常用这是我们写 get/set 方法时必用的用法也是封装的基础。为什么会有区分问题Java中有就近原则方法里出现同名变量时会优先使用离得更近的局部变量方法参数。如果直接写price price;相当于参数自己给自己赋值成员变量根本没被修改封装的赋值逻辑就失效了。比如⬇️public class MilkTea { // 私有属性private彻底隐藏外部不能直接碰 private String taste; // 口味 private double price; // 价格 private String secretFormula; // 秘制配方只有类内部能访问 // 此时你要调用该方法设置价格 public void setPrice(double price) { // ❌ 没有 this遵循就近原则参数自己赋给自己成员变量纹丝不动 price price; //那么这个设置就没有任何的意义并没有将价格设置成功 // ✅ 有 this明确指定把参数的值赋给当前对象的成员变量 this.price price; //此时才能真正的将价格设置成功 } }this.属性名就明确表示「我要访问的是当前对象的成员变量」和局部变量彻底区分开。 一句话等号左边加this的是对象的属性右边是传进来的值。用法2调用本类的其他构造方法不仅普通方法里能用this构造方法之间也可以通过this(参数)互相调用复用初始化逻辑减少重复代码让封装的初始化逻辑更统一。public class MilkTea { private String taste; private double price; // 单参构造调用全参构造默认价格设为12元 //也就是当你创建一个新的实例时你只传了taste参数会默认的价格 public MilkTea(String taste) { this(taste, 12.0); } // 全参构造完成所有属性的最终初始化 public MilkTea(String taste, double price) { this.taste taste; this.price price; } }必须遵守的两条规则this(参数)只能写在构造方法的第一行不能写在普通方法里禁止循环调用A构造调BB构造又调A会直接编译报错用法3代表当前对象本身this单独使用时就代表当前对象的引用本身可以当作返回值、方法参数传递。最典型的应用场景是链式编程每个 set 方法都返回this就可以连续调用多个 set 方法代码更简洁流畅。极简链式调用示例⬇️public MilkTea setTaste(String taste) { this.taste taste; return this; // 返回当前对象本身 } public MilkTea setPrice(double price) { this.price price; return this; }使用时可以一行代码设置多个属性// 链式调用连续设置多个属性 MilkTea tea new MilkTea().setTaste(珍珠).setPrice(12);这种写法在 Lombok 的Accessors(chain true)注解和建造者模式中非常常见日常封装基础属性时了解即可。3. 结合封装完整示例我们把封装的完整代码拿出来标注所有this的作用对应到实际开发场景public class MilkTea { private String taste; private double price; // 口味的set方法this.taste 代表成员变量taste 代表方法参数 public void setTaste(String taste) { this.taste taste; } // 价格的set方法加校验校验通过后用this赋值给成员变量 public void setPrice(double price) { if(price 0) { System.out.println(价格不能为负数); return; } this.price price; } // 普通方法里也可以用this访问本类的属性和其他方法 public void make() { System.out.println(制作 this.taste 奶茶); // this.调用本类私有方法this.也可以省略不写 this.checkPrice(); } private void checkPrice() { System.out.println(价格校验通过 this.price); } }4.容易踩的坑误区1静态方法里能用 this不记得什么是静态方法➡️Day11 --【Java全栈】方法 (下) 第5点✅ 正解static静态方法属于类不属于某个具体对象this代表当前对象所以静态方法里不能用this会直接编译报错。误区2变量名不一样就不用写 this故意起不同名字更省事✅ 正解如果参数名和成员变量名不一样比如参数叫newPrice不会产生歧义确实可以不用写this。不过行业规范推荐故意写成一样的名字再用this区分。因为setPrice(double newPrice)远不如setPrice(double price)直观——调用者一看方法名和参数就知道这是给 price 属性赋值的方法代码可读性更强。误区3this 就是对象本身✅ 正解this是对象的引用内存地址不是对象本体和之前讲的「引用变量 ≠ 对象本体」是同一个逻辑。它指向对象但不是对象本身。5. 和封装的关联总结封装的核心是「属性私有化通过公共方法访问」而this就是公共方法里操作私有属性的关键桥梁——没有this我们就没法在同名参数下准确给成员变量赋值封装的可控访问也就无从谈起。可以说private负责把属性藏起来this负责在内部精准操作属性二者配合完成完整的封装逻辑。五、封装的两大核心维度维度1属性封装数据封装—— 最基础、最常用就是我们最熟悉的属性用private私有化对外提供public的get/set方法。get方法getter读取属性值一般只读不修改数据set方法setter设置属性值里面可以加校验、转换、日志等逻辑保证数据合法。为什么不直接把属性设为public如果直接写成public double price;外部可以随便赋值比如tea.price -999;系统就会出现价格为负的脏数据而且你完全拦不住。用set方法封装后所有修改都必须走你的校验逻辑从根源上避免非法数据。 补充不是所有属性都要同时有get和set只允许读、不允许改的属性只写get方法比如订单创建时间生成后就不能改只允许内部改、外部不能改的属性只写set方法甚至都不写完全内部使用的属性get和set都不用写。维度2方法封装实现封装—— 进阶、更体现设计思想把复杂的内部执行步骤隐藏起来只对外暴露一个简单的总入口方法。调用者只要调用这个方法就行不用管里面分了多少步、每步怎么实现。顾客只要说「做一杯珍珠奶茶」调用public的make方法不用管里面先煮茶、再加奶、再加珍珠、摇杯这些内部步骤这些内部步骤都是private的外部看不到、也调用不了。public class MilkTea { private String taste; // 私有内部方法外部不能调用只有类内部自己用 private void boilTea() { System.out.println(1. 煮茶底); } private void addIngredients() { System.out.println(2. 加 taste 配料); } private void shakeCup() { System.out.println(3. 摇杯均匀); } // 对外暴露的公共入口只有这一个方法对外可见 public void makeMilkTea(String taste) { this.taste taste; // 内部步骤全部封装起来外部不用关心 boilTea(); addIngredients(); shakeCup(); System.out.println(奶茶制作完成); } }外部调用时只需要一行代码public class Test { public static void main(String[] args) { MilkTea tea new MilkTea(); // 只需要调用对外的方法不用管内部三步怎么执行 tea.makeMilkTea(珍珠); } }以后内部制作步骤升级了比如多加一步「封口」只要makeMilkTea这个入口不变外部调用的代码一行都不用改这就是封装带来的「低耦合」优势。 补充包的封装作用封装不仅体现在单个类内部包package也是 Java 里的一级封装单位。用默认访问权限不写修饰符即 default修饰的类或方法可以被同一个包下的其他类访问但包外的类完全看不到。这提供了一种比 public 更安全、比 private 更灵活的封装层级适合在同一个业务组件内部共享实现细节对外则隐藏内部实现。六、实际开发中的封装落地规范理解了封装的两层含义那在日常项目开发中封装有哪些通用的落地规范可以让我们的代码更严谨、更易维护呢1. 实体类POJO/DO的封装规范这是日常开发写得最多的对应数据库表的实体类所有成员变量全部用private修饰为需要访问的属性提供get/set方法构造方法、toString、equals、hashCode等公共方法用public实际开发中一般用 Lombok 的Data注解一键生成不用手写。这种写法也是JavaBean 规范的要求类必须是 public 的拥有 public 的无参构造方法属性通过 getXxx/setXxx 方法访问。遵守这个规范后Spring、MyBatis 等框架才能通过反射自动读取和设置对象的属性值。2. 业务层Service的封装规范核心业务逻辑封装在 service 层的 public 方法里controller 层只调用入口不直接操作数据库内部的校验逻辑、计算逻辑、数据转换逻辑抽成 private 的私有方法只在类内部复用不对外暴露。3. 工具类的封装规范工具类构造方法私有化private防止别人new对象所有工具方法用 public static 修饰对外直接调用内部的辅助计算方法用 private 隐藏只暴露最终可用的工具方法。七、不熟悉的同学容易踩的5个认知误区讲完了正确的实现方式和规范我们也梳理几个常见的认知偏差避免理解走偏。误区1封装就是写get/set方法✅ 正解get/set只是属性封装的一种实现方式是封装的入门级用法。封装还包括方法实现的隐藏、访问权限的控制、内部逻辑的隔离范围要大得多。误区2所有属性都必须写get和set✅ 正解按需暴露。只允许读的就只写get完全内部用的就都不写。盲目给所有属性都加set方法等于把数据的控制权又交出去了失去了封装的意义。误区3封装就是私有化藏得越深越好✅ 正解封装的核心是「可控」不是「完全隐藏」。该对外暴露的入口要清晰、稳定不该暴露的细节彻底藏起来过度封装会让代码变得冗余难用。准确地说封装追求的是信息隐藏Information Hiding——隐藏的是易变的实现细节暴露的是稳定的对外接口。实现细节可以随时优化调整只要对外接口保持不变就不会影响外部调用方的代码。❌ 过度封装的典型反例一个简单的属性赋值set 方法里又嵌套调用了 5 层私有方法层层跳转才能完成一次赋值逻辑没有任何复用价值反而大幅提升了代码的阅读成本——这就属于把简单问题复杂化的过度封装。误区4public方法里可以随便改内部属性✅ 正解哪怕是public方法修改内部属性也要保证数据合法。不能因为是对外方法就跳过校验所有修改内部状态的入口都要遵守数据校验规则。误区5封装只是面向对象的语法技巧✅ 正解封装不只是语法更是一种设计思想。小到一个类的属性方法大到一个模块、一个系统的接口设计核心思路都是「隐藏内部细节对外暴露稳定入口」是降低系统复杂度的核心手段。八、和之前知识点的串联1. 关联访问修饰符封装的底层实现就是靠四类访问权限通过权限控制实现「隐藏内部、暴露入口」。2. 关联构造方法构造方法也是封装的一部分——对象创建的初始化逻辑封装在构造方法里外部只要传参数就能得到合法对象不用关心内部赋值细节。3. 关联设计原则封装是「单一职责原则」「开闭原则」的基础——内部逻辑自己管、自己改不影响外部才能做到对扩展开放、对修改关闭。ps:今天好多本来想分两天的算了就这样吧