很多同学学完 Java 基础语法后,写代码依然像在写“高级 C 语言”——满屏的 static方法和零散的变量,完全没有发挥出面向对象的威力。
今天,我们通过一个完整的业务案例 + 渐进式代码演化,把面向对象的三大核心特性(封装、继承、多态)彻底串起来。文章不长,但每一个代码片段都值得你停下来思考。
![]()
一、封装:不仅要“藏起来”,还要“管起来”
场景:我们要做一个简单的员工管理系统。
错误示范(面向过程思维):
// 这样写,name 和 age 在外面想怎么改就怎么改,毫无安全感 class Employee { String name; int age; } // 使用时 Employee e = new Employee(); e.age = -10; // 年龄怎么能是负数?代码毫无约束力正确姿势(封装):
封装不仅仅是加 private,更重要的是提供受控的访问入口(Getter/Setter),并在入口处设立关卡。
class Employee { // 1. 属性私有化:隐藏内部实现 private String name; private int age; // 2. 提供公共的访问方法 public String getName() { return name; } public void setName(String name) { this.name = name; // this 区分成员变量与参数 } public int getAge() { return age; } public void setAge(int age) { // 3. 在方法内部做逻辑校验(封装业务规则) if (age >= 18 && age <= 60) { this.age = age; } else { throw new IllegalArgumentException("员工年龄必须在18-60岁之间"); } } }http://www.wx-tong.com/
-
private保证了数据的安全性。 -
this.age明确了当前对象的属性,避免了命名歧义。 - Setter 方法不再是简单的赋值,而是包含了业务逻辑(数据合法性检查)。
二、继承:从“重复代码”到“抽象共性”
现在需求变了,公司不仅有普通员工(Employee),还有经理(Manager)。经理也是员工,也有姓名年龄,但他还多了一个“奖金”属性。
笨办法:Copy-Paste 代码。但这样维护起来是灾难。
继承的办法:提取共性,建立层级。
// 父类:抽象出“员工”的共性 class Employee { private String name; private int age; // 构造方法 public Employee(String name, int age) { this.name = name; this.age = age; } // 共同的行为 public void work() { System.out.println(name + "正在工作..."); } // Getter/Setter 省略... } // 子类:在员工的基础上扩展 class Manager extends Employee { private double bonus; // 经理特有的奖金 // 子类的构造方法 public Manager(String name, int age, double bonus) { // super:调用父类的构造方法,初始化共有属性 super(name, age); this.bonus = bonus; } // 子类特有的方法 public void manage() { System.out.println(getName() + "正在管理团队,奖金:" + bonus); } }💡 代码解读:
-
extends建立了继承关系。 -
super(name, age)是关键:它显式调用了父类的构造器,确保父类部分先被初始化。如果父类有无参构造,这行代码甚至可以省略(编译器会自动加),但显式写出来是更好的习惯。 - 子类自动拥有了父类的
work()能力,同时扩展了自己的manage()能力。
三、多态:编译看左边,运行看右边
这是面向对象最精彩的部分。假设我们有一个方法,需要让所有员工开始工作。
没有多态的写法(糟糕):
public static void letThemWork(Employee e) { ... } public static void letThemWork(Manager m) { ... } // 每新增一个工种,就要新增一个方法,太麻烦!有多态的写法(优雅):
我们只需要一个接收父类(Employee)的方法,却可以传入任意子类对象。
// 1. 父类引用指向子类对象(向上转型) Employee emp1 = new Employee("张三", 25); Employee emp2 = new Manager("李四", 35, 10000); // 多态的核心体现 // 2. 定义一个统一的方法 public static void letThemWork(Employee employee) { // 3. 运行时绑定:到底执行谁的 work 方法? employee.work(); } // 调用 letThemWork(emp1); // 输出:张三正在工作... letThemWork(emp2); // 输出:李四正在工作...(虽然是 Employee 引用,但实际跑的是 Manager 继承来的逻辑)-
Employee emp2 = new Manager(...):http://www.wx-tong.com/这就是向上转型。编译器只看 Employee,所以你不能直接用 emp2.manage()(除非强转)。 - 动态绑定:虽然
emp2的编译类型是Employee,但运行时 JVM 知道它实际上是Manager。不过这里调用的是work(),它没有被重写,所以表现一致。
四、方法重写:多态的灵魂
上面的例子还不够震撼,因为 work()没变。现实中,经理的工作内容和员工肯定不一样。这就需要方法重写(Override)。
class Employee { protected String name; // 改为 protected,方便子类直接访问 public Employee(String name) { this.name = name; } // 父类的通用工作方法 public void work() { System.out.println(name + "正在处理基础事务..."); } } class Manager extends Employee { public Manager(String name) { super(name); } // 重写父类方法:@Override 注解能帮助编译器检查错误 @Override public void work() { // 可以在重写时调用父类原逻辑 // super.work(); System.out.println(name + "正在制定战略和管理团队..."); } } // 测试类 public class OfficeTest { public static void main(String[] args) { Employee staff1 = new Employee("张三"); Employee boss1 = new Manager("李四"); staff1.work(); // 输出:张三正在处理基础事务... boss1.work(); // 关键点:输出:李四正在制定战略和管理团队... // 编译看左(Employee有work),运行看右(实际执行Manager的work) } }💡 代码解读:
-
@Override:这个注解不是必须的,但强烈建议加上。如果方法签名写错(比如写成wrk),编译器会报错,防止低级 Bug。 -
super.work():在重写方法中,可以用super调用父类的原方法,实现逻辑的复用与增强。 - 多态生效的条件:继承 + 方法重写 + 父类引用指向子类对象。三者缺一不可。
五、总结:从代码看思想
让我们把这段代码映射到面向对象的精髓上:
- 封装:
private属性和getter/setter构成了对象的“防御体系”,保证了数据的完整性和业务的规范性。 - 继承:
extends建立了is-a关系(Manager is an Employee),消除了冗余代码,实现了代码的复用和功能的扩展。 - 多态:
Employee boss = new Manager()配合方法重写,使得我们可以写出通用的代码(letThemWork方法),从容应对未来的变化(比如新增CEO类,无需修改该方法)。
最后的忠告:
多态虽然强大,但也有代价。它牺牲了编译期的类型检查(无法直接调用子类独有方法),换取了运行期的灵活性。因此,不要为了用多态而用多态,只有当确实需要在“忽略具体类型,关注通用行为”时,多态才是最佳选择。