主题
Chapter 09 · 继承
"我妈是我妈" — 子类天然拥有父类的一切,还能加自己的东西。
🎯 本章目标
- 理解继承的本质:复用 + 扩展
- 掌握
extends关键字 - 学会
super的两种用法 - 区分 方法重写(Override)vs 重载(Overload)
- 知道
final关键字三大用法 - 理解
Object类是所有类的祖先
1. 为什么需要继承?
假设你要写"学生"和"老师"两个类:
java
class Student {
String name;
int age;
void eat() { ... }
void sleep() { ... }
void study() { ... }
}
class Teacher {
String name; // 重复
int age; // 重复
void eat() { ... } // 重复
void sleep() { ... } // 重复
void teach() { ... }
}90% 重复!再来"工程师"、"医生"……要疯。
抽出公共部分到父类 Person,让 Student 和 Teacher 继承 它:
java
class Person { // 父类(基类、超类)
String name;
int age;
void eat() { ... }
void sleep() { ... }
}
class Student extends Person { // 继承
void study() { ... }
}
class Teacher extends Person { // 继承
void teach() { ... }
}子类自动拥有父类的字段和方法,还能加自己的。
2. extends 关键字
java
class 子类 extends 父类 { ... }Java 继承的特点
- 单继承:一个类只能有一个直接父类(不像 C++ 多继承)
- 多层继承:可以 A → B → C → ... 一直延伸
- 不能跨级:孙子继承爷爷必须经过爸爸
- 构造器不被继承,但子类构造器会先调用父类构造器
java
class Animal { }
class Dog extends Animal { }
class Husky extends Dog { } // ✅ 多层继承
class Cat extends Animal, Dog { } // ❌ 不支持多继承3. super 关键字
super = "调用父类的东西",两大用法:
3.1 调用父类的方法 / 字段
java
class Person {
String name;
void greet() { System.out.println("Hi, I'm " + name); }
}
class Student extends Person {
String school;
@Override
void greet() {
super.greet(); // 先调父类的 greet
System.out.println("我在 " + school + " 读书");
}
}3.2 调用父类的构造器
java
class Person {
String name;
Person(String name) { this.name = name; }
}
class Student extends Person {
String school;
Student(String name, String school) {
super(name); // ① 必须是第一行
this.school = school; // ②
}
}⚠️ 隐式 super():如果子类构造器里没写
super(...),编译器会自动加super();(调用父类无参构造器)。如果父类没有无参构造器 → 编译错误!
4. 方法重写(Override)
子类对父类方法不满意?重写它!
java
class Animal {
void sound() { System.out.println("某种声音"); }
}
class Dog extends Animal {
@Override
void sound() { System.out.println("汪汪!"); }
}
class Cat extends Animal {
@Override
void sound() { System.out.println("喵喵!"); }
}
Animal a = new Dog();
a.sound(); // 输出: 汪汪!(不是"某种声音"!)重写规则(必背)
| 规则 | 说明 |
|---|---|
| 方法签名相同 | 名字 + 参数列表必须一致 |
| 返回类型 | 相同或父类返回类型的子类(协变返回) |
| 访问权限 | 不能比父类更严格(父 public,子不能 private) |
| 抛出异常 | 不能抛比父类更宽的检查异常 |
@Override | 强烈建议加(编译期检查) |
重写 vs 重载(高频面试)
| 对比 | 重写 Override | 重载 Overload |
|---|---|---|
| 位置 | 父子类 | 同一个类 |
| 方法名 | 相同 | 相同 |
| 参数列表 | 必须相同 | 必须不同 |
| 返回类型 | 相同/协变 | 无要求 |
| 绑定 | 运行时(动态分派) | 编译时(静态分派) |
| 关键字 | @Override | 无 |
5. final 关键字三大用法
final = "终极",封死,不准改:
| 修饰对象 | 含义 |
|---|---|
final 变量 | 常量,赋值后不能改 |
final 方法 | 不允许子类重写 |
final 类 | 不允许被继承(如 String、Integer) |
java
final class Constants { } // 不能被继承
class Calculator {
final void calculate() { } // 子类不能重写
}
final int MAX = 100; // 常量
final List<String> list = new ArrayList<>(); // 引用不能换,但内容能改
list.add("hi"); // ✅
list = new ArrayList<>(); // ❌6. Object 类:所有类的祖宗
所有类都隐式继承 Object(不写 extends 默认就是)。
Object 提供了几个所有类都有的方法:
| 方法 | 默认行为 | 是否常重写 |
|---|---|---|
toString() | 返回 类名@hashCode | ✅ 常重写 |
equals(Object) | 比较地址(== 等价) | ✅ 必重写(要比内容时) |
hashCode() | 返回内存地址 hash | ✅ 必和 equals 一起重写 |
getClass() | 返回 Class 对象 | 反射用 |
clone() | 浅拷贝 | 偶尔重写 |
toString 经典重写
java
class Person {
String name; int age;
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + "}";
}
}
System.out.println(new Person()); // 自动调 toString,不用 .toString()equals + hashCode 必须一起重写
🔑 黄金规则:两个对象
equals()为 true,hashCode 必须相等!
java
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Person)) return false;
Person p = (Person) o;
return age == p.age && Objects.equals(name, p.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}💡 IDE 一键生成(IDEA: Alt + Insert → equals and hashCode)。
7. 实战练习
| 文件 | 内容 |
|---|---|
Person.java | 父类:人 |
Student.java | 子类:学生 |
Teacher.java | 子类:老师 |
InheritanceDemo.java | 综合演示入口 |
ConstructorChain.java | super 构造器链 |
EqualsHashCodeDemo.java | equals/hashCode 必看 |
8. 浏览器演示
打开 demo.html:
- 继承关系树图
- 父类构造器调用顺序动画
- equals/hashCode 翻车与修复
9. 面试可能会问什么?
Q1: Java 为什么不支持多继承?
答:避免菱形继承问题(A 继承 B 和 C,而 B 和 C 都继承 D,A 调用 D 的方法时不知道走哪条路)。 Java 通过单继承 + 多接口 实现"伪多继承"。
Q2: 子类构造器一定会调父类构造器吗?
答:一定。如果你不写 super(...),编译器会自动加 super();。如果父类没有无参构造器,子类必须显式写 super(参数),否则编译失败。
Q3: 重写要满足什么规则?(即"两同两小一大")
- 两同:方法名相同、参数列表相同
- 两小:返回类型 ≤ 父类(协变),异常 ≤ 父类
- 一大:访问权限 ≥ 父类(不能更严格)
Q4: equals 和 == 的区别?
==:基本类型比值,引用类型比地址equals():默认与 == 等价;String/Integer 等重写后比内容
Q5: 为什么 equals 和 hashCode 必须一起重写?
答:HashMap、HashSet 等基于 hash 的容器:
- 用
hashCode算桶位置 - 用
equals在桶内比对
如果两个对象 equals 为 true 但 hashCode 不等,HashSet 就会重复存储 → bug。
Q6: final 类有哪些?
String、Integer、Long、Boolean、Math... 为什么?安全(防止子类覆盖关键行为)+ 性能(JIT 可以做更激进的优化)。
Q7: super.method() 和 this.method() 区别?
this.method():调用当前对象的方法(如果重写了,调子类的)super.method():强制调父类的版本,绕过子类重写
🎁 本章小结
✅ 继承 = 复用 + 扩展,extends 关键字
✅ super 调父类方法/构造器
✅ 重写 @Override:方法签名相同,访问权限不缩小
✅ final 三用:常量、方法不可重写、类不可继承
✅ Object 是所有类的爹,toString/equals/hashCode 三剑客🔗 导航
- ⬅️ 上一章:Chapter 08 · 封装
- ➡️ 下一章:Chapter 10 · 多态