目录
引言
在面向对象编程中,继承是代码复用和层次化设计的核心机制。通过继承,子类可以复用父类的属性和方法,同时扩展自身特性。本文将通过生动的例子和严谨的讲解,带你彻底掌握Java继承的方方面面,包括语法细节、初始化顺序、访问控制,以及与组合的对比。
一、为什么需要继承?
假设我们要描述“狗”和“猫”两种动物,它们的类中会有大量重复代码:
// Dog.java public class Dog { String name; int age; float weight; public void eat() { System.out.println(name + "正在吃饭"); } public void sleep() { System.out.println(name + "正在睡觉"); } void bark() { System.out.println(name + "汪汪汪~~~"); } } // Cat.java public class Cat { String name; int age; float weight; public void eat() { System.out.println(name + "正在吃饭"); } public void sleep() { System.out.println(name + "正在睡觉"); } void mew() { System.out.println(name + "喵喵喵~~~"); } }
问题:name
、age
、weight
以及eat()
、sleep()
方法重复出现。 解决:抽取共性,定义父类Animal
,子类Dog
和Cat
继承它。
二、继承的基本概念
-
父类(基类/超类):如
Animal
,包含公共属性和方法。 -
子类(派生类):如
Dog
和Cat
,继承父类并扩展特有功能。
// Animal.java public class Animal { String name; int age; float weight; public void eat() { System.out.println(name + "正在吃饭"); } public void sleep() { System.out.println(name + "正在睡觉"); } } // Dog.java public class Dog extends Animal { void bark() { System.out.println(name + "汪汪汪~~~"); } } // Cat.java public class Cat extends Animal { void mew() { System.out.println(name + "喵喵喵~~~"); } }
核心作用:
-
代码复用:子类直接继承父类成员。
-
支持多态(后续讲解)。
三、继承的语法与细节
1. 语法规则
使用extends
关键字:
修饰符 class 子类 extends 父类 { ... }
2. 父类成员访问
-
成员变量:
-
子类与父类变量同名时,优先访问子类变量(就近原则)。
-
使用
super
访问父类同名变量:super.a = 10;
-
Java中的成员变量不会被覆盖,而是被隐藏。当子类和父类有同名变量时,子类的变量会隐藏父类的变量。直接使用a的时候访问的是子类的变量,而使用super.a才能访问父类的变量
-
成员方法:
-
方法名不同时,子类优先访问自身方法,找不到则向父类查找。
-
方法名相同且参数列表一致时,子类重写(Override)父类方法。
-
2.1. 访问规则
private
修饰的父类变量
如果父类变量是 private
,子类无法直接访问(包括通过 super
)
解决方法:通过父类的 protected/public
方法间接访问。
2.2. 重写规则
-
访问权限:子类方法的访问修饰符不能比父类更严格(如父类是
public
,子类不能是protected
)。 -
返回类型:必须与父类相同,或为父类返回类型的子类(协变返回类型)。
-
异常处理:子类方法抛出的异常不能比父类更宽泛。
-
静态方法:不能被重写(会隐藏父类静态方法,属于“方法隐藏”)。
-
final
方法:父类声明为final
的方法不可被重写。 -
private
方法:父类私有方法对子类不可见,无法重写。
四、super关键字:穿透继承迷雾
1. 核心作用
-
访问父类的成员变量和方法。
-
调用父类构造方法。
2. 示例
public class Derived extends Base { int a; // 与父类同名变量 void method() { a = 100; // 访问子类a super.a = 200; // 访问父类a super.methodB(); // 调用父类方法 } }
3. 构造方法中的super
子类构造方法必须先调用父类构造方法:
public class Derived extends Base { public Derived() { super(10); // 必须为第一条语句 System.out.println("子类构造方法"); } }
规则:
-
若父类有无参构造,子类可省略
super()
(编译器自动添加)。 -
若父类只有带参构造,子类必须显式调用
super(参数)
。
五、初始化顺序:代码块与构造方法
1. 执行顺序(继承体系下)
-
父类静态代码块 → 子类静态代码块(只执行一次)。
-
父类实例代码块 → 父类构造方法。
-
子类实例代码块 → 子类构造方法。
示例:
// 执行结果: Person: 静态代码块执行 → 父类静态 Student: 静态代码块执行 → 子类静态 Person: 实例代码块执行 → 父类实例 Person: 构造方法执行 → 父类构造 Student: 实例代码块执行 → 子类实例 Student: 构造方法执行 → 子类构造
六、访问修饰符与继承
修饰符 | 本类 | 同包 | 不同包子类 | 不同包非子类 |
---|---|---|---|---|
private | ✔️ | ❌ | ❌ | ❌ |
default | ✔️ | ✔️ | ❌ | ❌ |
protected | ✔️ | ✔️ | ✔️ | ❌ |
public | ✔️ | ✔️ | ✔️ | ✔️ |
关键点:
-
protected
成员在不同包中,只能通过子类对象访问。 -
父类
private
成员对子类不可见。
七、继承方式与final关键字
1. Java支持的继承方式
-
单继承:一个子类只能有一个父类。
-
多层继承:
A → B → C
。 -
不同类继承同一父类:
B → A
,C → A
。
不支持多继承!
// 错误示例 class C extends A, B { ... } // 编译报错
2. final关键字
-
修饰类:类不能被继承(如
String
)。 -
修饰方法:方法不能被重写。
-
修饰变量:变量变为常量。
final public class Animal { ... } // 不能被继承
八、组合:另一种复用方式
1. 组合 vs 继承
-
继承(is-a):狗是动物。
-
组合(has-a):汽车有发动机、轮胎。
2. 组合示例
class Tire { ... } class Engine { ... } class Car { private Tire tire; private Engine engine; // 通过组合复用功能 } class Benz extends Car { ... } // 奔驰是汽车
最佳实践:优先使用组合,避免过深的继承层次。
九、巩固练习
-
补全构造方法:确保子类正确调用父类构造。
-
重写计算逻辑:练习方法重写(Override)。
-
选择题:
-
Q:关于Java继承,正确的是? A. 一个类可以同时继承多个类 ❌ D. 子类可以重写父类的方法 ✔️
-
Q:错误的说法是? C. 构造方法可以被子类继承 ❌(构造方法不可继承!)
-
结语
继承是Java面向对象设计的基石,理解其细节能大幅提升代码质量。记住:
-
慎用继承,避免过度设计。
-
多用组合,保持灵活性。
-
封装优先,合理使用访问修饰符。
希望这篇博客能让你对继承的理解更上一层楼! 🚀
进一步学习:
-
Java官方文档:继承与多态