- 0
- 238 words
假设你要开发一个员工管理系统。张三是一名普通员工,李四是一名经理,王五是一名技术总监。
- 普通员工有:姓名、工号、工资、打卡、领工资
- 经理有:姓名、工号、工资、打卡、领工资、管理团队、审批报销
- 技术总监有:姓名、工号、工资、打卡、领工资、制定技术方案、面试候选人
写三个独立类,你会写出大量重复代码——三个类都有姓名、工号、工资,都有打卡和领工资的方法。
这不合理。现实世界中”经理”是”员工”的一种特殊类型——经理”是“(is-a)员工,只是比普通员工多了某些功能。
继承正是为了解决这个问题而存在的:把公共属性和方法抽取到父类(基类/超类),子类(派生类)通过 extends 关键字继承父类,并在此基础上扩展自己的特性。
为什么需要继承
在编写了前三篇类与对象的内容后,你已经会用类来描述现实世界的实体。但遇到多个类有共性时,逐个编写会导致:
- 代码冗余——同样的字段和方法在多个类中重复
- 维护困难——修改公共逻辑需要到处同步
- 扩展性差——新增一种员工类型又得复制一遍
继承让代码可复用、可扩展、能分层。
extends 关键字与继承语法
Java 使用 extends 关键字实现继承。
基本语法
// 父类
class Employee {
String name;
int id;
double salary;
void punchClock() {
System.out.println(name + " 打卡上班");
}
void getPaid() {
System.out.println(name + " 领取工资:" + salary);
}
}
// 子类继承父类
class Manager extends Employee {
int teamSize;
void manageTeam() {
System.out.println(name + " 管理 " + teamSize + " 人团队");
}
void approveExpense() {
System.out.println(name + " 审批报销");
}
}
👉 Manager 没有定义 name、salary、punchClock(),但这些它都有——从 Employee 继承而来。
测试继承
public class InheritanceDemo {
public static void main(String[] args) {
Manager mgr = new Manager();
mgr.name = "李四"; // 继承自 Employee
mgr.salary = 25000; // 继承自 Employee
mgr.teamSize = 8; // 自己的字段
mgr.punchClock(); // 继承自 Employee
mgr.manageTeam(); // 自己的方法
}
}
输出:
李四 打卡上班
李四 管理 8 人团队
继承的核心特性
① 子类拥有父类所有的非 private 成员
| 访问修饰符 | 当前类 | 同包 | 子类 | 其他包 |
|---|---|---|---|---|
private | ✅ | ❌ | ❌ | ❌ |
| 默认(包级) | ✅ | ✅ | ❌ | ❌ |
protected | ✅ | ✅ | ✅ | ❌ |
public | ✅ | ✅ | ✅ | ✅ |
子类无法继承 private 成员,但可以通过父类提供的 public/protected 方法间接访问。
② Java 只支持单继承
class A extends B, C { } // ❌ 编译错误!Java 不支持多继承
一个子类只能有一个直接父类。那如果确实需要”多重特性”怎么办?后续文章会讲——接口(interface)可以解决。
③ 继承关系是 “is-a” 关系
适用场景用一句话判断:子类 is-a 父类。
Manager is-a Employee? ✅ 成立
Dog is-a Animal? ✅ 成立
Circle is-a Shape? ✅ 成立
如果这句话读不通,就不应该用继承——应该用组合(后面会详细讲)。
④ 传递性
class A { }
class B extends A { }
class C extends B { }
C 继承了 B 的所有非私有成员,B 继承了 A 的所有非私有成员——所以 C 间接也拥有 A 的成员。这叫多层继承。
每个类都隐式继承 Object
你可能没写过 extends,但每个 Java 类都默认继承 java.lang.Object。
class MyClass { } // 等价于 class MyClass extends Object { }
Object 提供了几个所有对象都有的方法:
| 方法 | 作用 |
|---|---|
toString() | 返回对象的字符串表示 |
equals(Object obj) | 比较两个对象是否”相等” |
hashCode() | 返回对象的哈希码 |
getClass() | 返回对象的运行时类信息 |
clone() | 创建并返回对象的一个副本 |
public class ObjectDemo {
public static void main(String[] args) {
Employee emp = new Employee();
System.out.println(emp.toString()); // 来自 Object
System.out.println(emp.getClass()); // 来自 Object
}
}
输出类似:
Employee@1b6d3586
class Employee
toString() 默认输出”类名@哈希值”——后续文章会讲如何重写它,让它输出更有意义的信息。
💡 思考:既然所有类都继承
Object,那int、double等基本类型呢?它们不是类,不继承Object。但它们的包装类(Integer、Double)继承Object——第 ⑧ 篇会讲枚举与包装类。
继承 vs 组合(Composition)
这是一个常被忽视的设计思考题。很多初学者一发现继承好用就到处用,但其实优先使用组合,谨慎使用继承。
什么是组合?
组合就是在一个类中持有另一个类的引用,表示”has-a“关系。
// 组合 - 汽车有引擎
class Engine {
void start() { System.out.println("引擎启动"); }
}
class Car {
private Engine engine = new Engine(); // 组合
void start() {
engine.start();
System.out.println("汽车启动");
}
}
什么时候用继承,什么时候用组合?
| 场景 | 用继承 | 用组合 | 原因 |
|---|---|---|---|
| 经理”是”一种员工 | ✅ | is-a 关系 | |
| 汽车”有”引擎 | ✅ | has-a 关系 | |
| 需要复用一个类的实现 | ✅ | 组合更灵活,不破坏封装 | |
| 需要利用多态(后续篇讲) | ✅ | 多态依赖继承 | |
| 只想复用代码,不是表达is-a | ✅ | 组合是更好的重用方式 |
一个反例
// ❌ 不要为了复用 getTotalPrice() 就让 Order 继承 ArrayList
class Order extends ArrayList<Item> { ... }
Order 不是一种 ArrayList——它是”订单有商品列表”,应该用组合:
// ✅ 组合更合理
class Order {
private List<Item> items = new ArrayList<>();
// ...
}
💡 一句话原则:继承是用来表达”是什么”的,不是用来”拿代码”的。
本篇小结
今天你学到了:
- 继承的本质——通过
extends关键字让子类复用父类的非私有成员,表达 is-a 关系 - 语法——
class Manager extends Employee即可完成继承 - 访问性——子类可以访问父类的
public和protected成员,不能访问private - 单继承——Java 一个子类只能有一个直接父类
- Object 祖先——所有类默认继承
java.lang.Object - 继承 vs 组合——能用组合优先用组合,继承只用在 is-a 场景
下一篇预告: 继承不能只靠继承字段和方法——子类对象如何初始化?父类构造器和子类构造器谁先执行?super 关键字如何灵活调用父类成员?
下一篇 《继承进阶:super 关键字与构造器链》,带你深入继承的底层机制。