目录
[一、为什么需要继承?------ 解决代码冗余问题](#一、为什么需要继承?—— 解决代码冗余问题)
[4.1 子类访问父类的成员变量](#4.1 子类访问父类的成员变量)
[场景 1:子类和父类无同名成员变量](#场景 1:子类和父类无同名成员变量)
[场景 2:子类和父类有同名成员变量](#场景 2:子类和父类有同名成员变量)
[4.2 子类访问父类的成员方法](#4.2 子类访问父类的成员方法)
[场景 1:子类和父类无同名成员方法](#场景 1:子类和父类无同名成员方法)
[场景 2:子类和父类有同名成员方法](#场景 2:子类和父类有同名成员方法)
[五、super 关键字:访问父类的同名成员](#五、super 关键字:访问父类的同名成员)
[5.1 super 的核心使用场景](#5.1 super 的核心使用场景)
[场景 1:访问父类的同名成员变量](#场景 1:访问父类的同名成员变量)
[场景 2:访问父类的同名成员方法](#场景 2:访问父类的同名成员方法)
[场景 3:调用父类的构造方法](#场景 3:调用父类的构造方法)
[5.2 super 关键字完整示例](#5.2 super 关键字完整示例)
[5.3 super 的注意事项](#5.3 super 的注意事项)
[6.1 子类构造方法的核心规则](#6.1 子类构造方法的核心规则)
[6.2 子类构造方法示例](#6.2 子类构造方法示例)
[示例 1:父类有无参构造(默认 / 显式)](#示例 1:父类有无参构造(默认 / 显式))
[示例 2:父类只有有参构造(必须显式调用)](#示例 2:父类只有有参构造(必须显式调用))
[七、super 和 this 的对比](#七、super 和 this 的对比)
[8.1 初始化相关的代码块回顾](#8.1 初始化相关的代码块回顾)
[8.2 继承体系的初始化核心规则](#8.2 继承体系的初始化核心规则)
[8.3 继承体系初始化完整示例](#8.3 继承体系初始化完整示例)
[九、访问修饰符:protected 与继承的可见性](#九、访问修饰符:protected 与继承的可见性)
[9.1 4 种访问修饰符的访问范围](#9.1 4 种访问修饰符的访问范围)
[9.2 protected 的核心特性](#9.2 protected 的核心特性)
[步骤 1:创建包 1 的父类 Base(protected 修饰成员)](#步骤 1:创建包 1 的父类 Base(protected 修饰成员))
[步骤 2:创建包 2 的子类 Derived(可访问自身的 b)](#步骤 2:创建包 2 的子类 Derived(可访问自身的 b))
[9.3 访问修饰符的使用原则](#9.3 访问修饰符的使用原则)
[十、Java 的继承方式与 final 关键字](#十、Java 的继承方式与 final 关键字)
[10.1 Java 支持的继承方式](#10.1 Java 支持的继承方式)
[10.2 final 关键字:限制继承](#10.2 final 关键字:限制继承)
[final 修饰类的示例(禁止继承)](#final 修饰类的示例(禁止继承))
[10.3 继承的层次建议](#10.3 继承的层次建议)
[11.1 组合的实现方式](#11.1 组合的实现方式)
[11.2 继承与组合的对比示例](#11.2 继承与组合的对比示例)
[11.3 继承与组合的选择原则](#11.3 继承与组合的选择原则)
[十二、Java 继承高频面试题解析](#十二、Java 继承高频面试题解析)
[题 1:以下关于 Java 继承的说法,正确的是?(D)](#题 1:以下关于 Java 继承的说法,正确的是?(D))
[题 2:以下关于 Java 继承的说法,错误的是?(C)](#题 2:以下关于 Java 继承的说法,错误的是?(C))
[题 3:子类构造方法中,super (...) 的使用要求是什么?](#题 3:子类构造方法中,super (...) 的使用要求是什么?)
[题 4:继承体系中,静态代码块、实例代码块、构造方法的执行顺序是什么?](#题 4:继承体系中,静态代码块、实例代码块、构造方法的执行顺序是什么?)
在 Java 面向对象的三大特性(封装、继承、多态)中,继承是实现代码复用、构建类层次结构的核心机制。它让我们可以基于已有类的特性扩展出新的类,避免重复编写相同代码,同时为多态的实现奠定基础。本文将从继承的核心需求出发,由浅入深讲解继承的语法、使用细节、核心关键字以及与组合的区别,结合完整可运行的代码示例,让你彻底吃透 Java 继承的精髓。
一、为什么需要继承?------ 解决代码冗余问题
在开发中,我们会用类描述现实世界的实体,但很多实体之间存在共性。比如狗和猫,都属于动物,都有name、age等属性,也有eat()、sleep()等行为。如果单独为每个实体编写类,会出现大量重复代码。
无继承的弊端:代码重复率高
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 + "汪汪汪~~~");
}
}
// 猫类
public class Cat {
String name;
int age;
float weight;
// 与Dog类重复的吃饭方法
public void eat() {
System.out.println(name + "正在吃饭");
}
// 与Dog类重复的睡觉方法
public void sleep() {
System.out.println(name + "正在睡觉");
}
// 猫的特有方法:叫
void mew() {
System.out.println(name + "喵喵喵~~~");
}
}
观察上述代码,Dog和Cat类中60% 以上的代码是重复的 ,这些共性的属性和方法分散在各个类中,后期维护成本极高(比如修改eat()方法的逻辑,需要修改所有相关类)。
而继承 的核心就是抽取共性,实现代码复用,让子类只需关注自身的特有特性即可。
二、继承的核心概念
- 继承机制 :允许程序员在保持原有类特性的基础上扩展新功能,生成新的类。原有类称为父类 / 基类 / 超类 ,新类称为子类 / 派生类。
- 核心作用:① 实现代码复用;② 为多态提供基础。
- 关系描述 :子类和父类是is-a的关系(比如狗是动物、猫是动物)。
- 核心特性:子类会继承父类的非私有成员(属性和方法),子类可在父类基础上新增自己的特有成员。
类层次结构示意:
Animal(父类):属性(name/age/weight)、方法(eat/sleep)
↗️ ↘️
Dog(子类):新增bark()方法 Cat(子类):新增mew()方法
三、继承的基本语法
在 Java 中,使用extends关键字表示类之间的继承关系,语法格式如下:
java
// 修饰符 子类名 extends 父类名 { 子类特有成员 }
修饰符 class SubClass extends SuperClass {
// 子类新增的属性和方法
}
基于继承重构猫狗案例
我们抽取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 + "正在睡觉");
}
}
/**
* 狗子类:继承Animal父类,仅实现特有方法
*/
public class Dog extends Animal {
// 狗的特有方法:汪汪叫
void bark() {
System.out.println(name + "汪汪汪~~~");
}
}
/**
* 猫子类:继承Animal父类,仅实现特有方法
*/
public class Cat extends Animal {
// 猫的特有方法:喵喵叫
void mew() {
System.out.println(name + "喵喵喵~~~");
}
}
/**
* 测试类:验证继承的代码复用效果
*/
public class TestExtend {
public static void main(String[] args) {
// 创建狗对象
Dog dog = new Dog();
// 直接使用父类继承的属性
dog.name = "大黄";
dog.age = 3;
// 直接使用父类继承的方法
dog.eat();
dog.sleep();
// 调用子类特有方法
dog.bark();
System.out.println("==========分割线==========");
// 创建猫对象
Cat cat = new Cat();
// 直接使用父类继承的属性
cat.name = "小白";
cat.age = 2;
// 直接使用父类继承的方法
cat.eat();
cat.sleep();
// 调用子类特有方法
cat.mew();
}
}
运行结果:
大黄正在吃饭
大黄正在睡觉
大黄汪汪汪~~~
==========分割线==========
小白正在吃饭
小白正在睡觉
小白喵喵喵~~~
核心亮点:子类无需重新定义父类的共性成员,直接继承使用,代码量大幅减少,维护性提升。
继承的基础注意事项
- 子类会继承父类的非私有成员变量和成员方法(私有成员被封装,子类无法直接访问);
- 子类继承父类后,必须新增自身特有成员,否则就没有继承的意义(直接使用父类即可);
- Java 中一个子类只能有一个直接父类(单继承),不支持多继承。
四、父类成员的访问规则
子类继承了父类的成员,那在子类中如何访问这些成员?是否存在优先级?核心遵循就近原则 ,同时分成员变量 和成员方法两种场景分析。
4.1 子类访问父类的成员变量
场景 1:子类和父类无同名成员变量
子类可直接访问父类的成员变量,无任何限制:
java
/**
* 父类Base
*/
public class Base {
int a;
int b;
}
/**
* 子类Derived:无同名成员变量
*/
public class Derived extends Base {
int c; // 子类特有变量
public void method() {
a = 10; // 直接访问父类的a
b = 20; // 直接访问父类的b
c = 30; // 访问子类自己的c
}
}
场景 2:子类和父类有同名成员变量
当子类存在与父类同名的成员变量时,优先访问子类自己的成员变量;若子类无该变量,才会访问父类的,父类也无则编译报错。
java
/**
* 父类Base
*/
public class Base {
int a = 10;
int b = 20;
int c = 30;
}
/**
* 子类Derived:有同名成员变量
*/
public class Derived extends Base {
int a = 100; // 与父类a同名同类型
char b = 'b';// 与父类b同名不同类型
public void method() {
a = 200; // 访问子类自己的a,而非父类的a
b = 'c'; // 访问子类自己的b,而非父类的b
c = 400; // 子类无c,访问父类的c
// d = 500; // 编译报错:父类和子类都无d
}
}
4.2 子类访问父类的成员方法
场景 1:子类和父类无同名成员方法
子类可直接访问父类的成员方法,无任何限制,遵循自己没有找父类的原则:
java
/**
* 父类Base
*/
public class Base {
public void methodA() {
System.out.println("Base中的methodA()");
}
}
/**
* 子类Derived:无同名方法
*/
public class Derived extends Base {
public void methodB() {
System.out.println("Derived中的methodB()");
}
public void methodC() {
methodB(); // 访问子类自己的methodB
methodA(); // 子类无methodA,访问父类的methodA
// methodD(); // 编译报错:整个继承体系无methodD
}
}
场景 2:子类和父类有同名成员方法
当子类存在与父类同名的成员方法时,优先访问子类自己的方法 ;若方法是重载 (参数列表不同),则根据传参匹配方法;若方法是重写(参数列表、返回值、方法名完全相同),则直接访问子类的重写方法。
java
/**
* 父类Base
*/
public class Base {
public void methodA() {
System.out.println("Base中的methodA()");
}
public void methodB() {
System.out.println("Base中的methodB()");
}
}
/**
* 子类Derived:有同名方法
*/
public class Derived extends Base {
// 重写父类的methodA(参数列表、方法名完全相同)
public void methodA() {
System.out.println("Derived中的methodA()");
}
// 重写父类的methodB
public void methodB() {
System.out.println("Derived中的methodB()");
}
public void methodC() {
methodA(); // 访问子类重写的methodA
methodB(); // 访问子类重写的methodB
}
}
五、super 关键字:访问父类的同名成员
当子类和父类存在同名成员 时,子类无法直接访问父类的同名成员,此时需要使用super 关键字 ------ 它的核心作用是在子类中访问父类的成员(变量 / 方法 / 构造方法) ,相当于子类对象中父类继承部分的引用。
5.1 super 的核心使用场景
场景 1:访问父类的同名成员变量
java
super.父类成员变量名
场景 2:访问父类的同名成员方法
java
super.父类成员方法名(参数)
场景 3:调用父类的构造方法
java
super(参数); // 必须是子类构造方法的第一行
5.2 super 关键字完整示例
java
/**
* 父类Base
*/
public class Base {
int a = 10;
int b = 20;
public void methodA() {
System.out.println("Base中的methodA()");
}
public void methodB() {
System.out.println("Base中的methodB()");
}
}
/**
* 子类Derived:使用super访问父类同名成员
*/
public class Derived extends Base {
int a = 100; // 与父类a同名
// 与父类methodA重载(参数列表不同)
public void methodA(int a) {
System.out.println("Derived中的methodA(int):" + a);
}
// 与父类methodB重写(原型完全相同)
public void methodB() {
System.out.println("Derived中的methodB()");
}
public void methodC() {
// 1. 访问成员变量:直接访问=子类自己,super=父类
a = 200; // 等价于this.a = 200,访问子类的a
super.a = 300; // 访问父类的a
b = 400; // 子类无b,直接访问父类的b(也可写super.b)
// 2. 访问成员方法:重载通过参数区分,重写通过super访问父类
methodA(); // 无参,访问父类的methodA
methodA(500); // 有参,访问子类的methodA(int)
methodB(); // 直接访问,子类重写的methodB
super.methodB();// super,访问父类的methodB
}
}
/**
* 测试类
*/
public class TestSuper {
public static void main(String[] args) {
Derived d = new Derived();
d.methodC();
}
}
运行结果:
Base中的methodA()
Derived中的methodA(int):500
Derived中的methodB()
Base中的methodB()
5.3 super 的注意事项
- 只能在非静态方法中使用:静态方法属于类,不依赖对象,而 super 是对象层面的引用;
- 不能和 this 同时在构造方法中使用:两者都要求是构造方法的第一行;
- super 不是对象:只是一个指向父类继承部分的关键字,无法单独打印或赋值。

六、子类的构造方法:先有父,后有子
在 Java 中,构造子类对象时,必须先构造父类对象------ 因为子类的成员由「父类继承的部分」+「子类新增的部分」组成,只有先初始化父类成员,才能完整初始化子类对象。
6.1 子类构造方法的核心规则
- 默认调用父类无参构造 :如果子类构造方法中没有显式调用父类构造方法,编译器会自动在子类构造方法的第一行添加
super(),调用父类的无参构造; - 显式调用父类有参构造 :如果父类没有无参构造 (显式定义了有参构造),子类必须在构造方法的第一行 通过
super(参数)显式调用父类的有参构造,否则编译报错; - super (...) 只能出现一次 :子类构造方法中,
super(...)只能写一次,且必须是第一行代码; - 不能和 this (...) 同时使用 :
this(...)是调用子类自己的其他构造方法,也要求是第一行,两者冲突。
6.2 子类构造方法示例
示例 1:父类有无参构造(默认 / 显式)
java
/**
* 父类Base:显式无参构造
*/
public class Base {
public Base() {
System.out.println("Base的无参构造执行");
}
}
/**
* 子类Derived:默认调用父类无参构造
*/
public class Derived extends Base {
public Derived() {
// 编译器自动添加super(),无需手动写
System.out.println("Derived的无参构造执行");
}
}
/**
* 测试类
*/
public class TestConstructor1 {
public static void main(String[] args) {
Derived d = new Derived();
}
}
运行结果:
Base的无参构造执行
Derived的无参构造执行
示例 2:父类只有有参构造(必须显式调用)
java
/**
* 父类Base:只有有参构造,无默认无参构造
*/
public class Base {
int a;
public Base(int a) {
this.a = a;
System.out.println("Base的有参构造执行:a=" + a);
}
}
/**
* 子类Derived:必须显式调用父类有参构造
*/
public class Derived extends Base {
public Derived() {
super(10); // 显式调用父类有参构造,必须是第一行
System.out.println("Derived的无参构造执行");
}
public Derived(int a, int b) {
super(a); // 显式调用父类有参构造
System.out.println("Derived的有参构造执行:b=" + b);
}
}
/**
* 测试类
*/
public class TestConstructor2 {
public static void main(String[] args) {
Derived d1 = new Derived();
System.out.println("==========分割线==========");
Derived d2 = new Derived(20, 30);
}
}
运行结果:
Base的有参构造执行:a=10
Derived的无参构造执行
==========分割线==========
Base的有参构造执行:a=20
Derived的有参构造执行:b=30
七、super 和 this 的对比
super 和 this 都是 Java 的核心关键字,都能访问成员变量 / 方法,也能用于构造方法,两者的异同点如下表所示,帮你彻底区分:
| 对比维度 | this | super |
|---|---|---|
| 核心含义 | 代表当前对象的引用 | 代表子类对象中父类继承部分的引用 |
| 访问成员 | 访问本类的成员(变量 / 方法),本类无则找父类 | 直接访问父类的成员(变量 / 方法) |
| 构造方法 | this(...)调用本类的其他构造方法 |
super(...)调用父类的构造方法 |
| 存在位置 | 非静态方法 / 构造方法中 | 非静态方法 / 构造方法中 |
| 第一行规则 | 构造方法中使用时,必须是第一行 | 构造方法中使用时,必须是第一行 |
| 共存性 | 不能和 super (...) 同时在构造方法中使用 | 不能和 this (...) 同时在构造方法中使用 |
| 编译器默认添加 | 构造方法中不默认添加,需手动写 | 子类构造方法中默认添加 super ()(父类有无参构造时) |
核心总结 :this关注本类 ,super关注父类,两者都是面向对象中封装和继承的重要体现。
八、继承体系中的初始化顺序
在 Java 中,类的初始化包含静态代码块 、实例代码块 、构造方法,当存在继承关系时,初始化顺序会有明确的优先级,这是面试高频考点,必须牢牢掌握。
8.1 初始化相关的代码块回顾
- 静态代码块 :用
static{}修饰,类加载时执行,仅执行一次,用于初始化静态变量; - 实例代码块 :用
{}修饰,创建对象时执行,每次创建对象都会执行,执行在构造方法之前,用于初始化实例变量; - 构造方法 :创建对象时执行,每次创建对象都会执行,用于初始化对象的完整状态。
8.2 继承体系的初始化核心规则
优先级从高到低:
- 父类静态代码块 → 子类静态代码块(静态代码块仅执行一次,类加载时完成);
- 父类实例代码块 → 父类构造方法(初始化父类继承部分);
- 子类实例代码块 → 子类构造方法(初始化子类新增部分);
- 再次创建子类对象时,静态代码块不再执行(仅类加载时执行一次),其余步骤重复。
8.3 继承体系初始化完整示例
java
/**
* 父类Person
*/
public class Person {
public String name;
public int age;
// 静态代码块
static {
System.out.println("Person:静态代码块执行");
}
// 实例代码块
{
System.out.println("Person:实例代码块执行");
}
// 构造方法
public Person(String name, int age) {
this.name = name;
this.age = age;
System.out.println("Person:构造方法执行");
}
}
/**
* 子类Student:继承Person
*/
public class Student extends Person {
// 静态代码块
static {
System.out.println("Student:静态代码块执行");
}
// 实例代码块
{
System.out.println("Student:实例代码块执行");
}
// 构造方法:必须显式调用父类有参构造
public Student(String name, int age) {
super(name, age);
System.out.println("Student:构造方法执行");
}
}
/**
* 测试类:验证初始化顺序
*/
public class TestInitOrder {
public static void main(String[] args) {
System.out.println("=====第一次创建Student对象=====");
Student s1 = new Student("张三", 19);
System.out.println("=====第二次创建Student对象=====");
Student s2 = new Student("李四", 20);
}
}
运行结果:
=====第一次创建Student对象=====
Person:静态代码块执行
Student:静态代码块执行
Person:实例代码块执行
Person:构造方法执行
Student:实例代码块执行
Student:构造方法执行
=====第二次创建Student对象=====
Person:实例代码块执行
Person:构造方法执行
Student:实例代码块执行
Student:构造方法执行
结果分析:静态代码块仅在第一次创建对象时执行(类加载),第二次创建对象时不再执行,完全符合初始化规则。
九、访问修饰符:protected 与继承的可见性
在 Java 中,有 4 种访问修饰符:private、default(包访问)、protected、public,它们决定了成员的访问范围,其中protected 是为继承专门设计的,核心作用是让不同包的子类能访问父类的成员。
9.1 4 种访问修饰符的访问范围
| 访问范围 | private | default | protected | public |
|---|---|---|---|---|
| 同一类中 | ✅ | ✅ | ✅ | ✅ |
| 同一包的不同类 | ❌ | ✅ | ✅ | ✅ |
| 不同包的子类 | ❌ | ❌ | ✅ | ✅ |
| 不同包的非子类 | ❌ | ❌ | ❌ | ✅ |
9.2 protected 的核心特性
protected修饰的成员,不同包的子类只能通过自身对象访问 ,不能访问其他子类对象的该成员,这是容易踩坑的点,示例说明:
步骤 1:创建包 1 的父类 Base(protected 修饰成员)
java
package com.demo1;
/**
* 父类:com.demo1包
*/
public class Base {
protected int b = 100; // protected修饰的成员变量
}
步骤 2:创建包 2 的子类 Derived(可访问自身的 b)
java
package com.demo2;
import com.demo1.Base;
/**
* 子类1:com.demo2包,继承Base
*/
public class Derived extends Base {
public static void main(String[] args) {
Derived d = new Derived();
System.out.println(d.b); // 合法:访问自身对象的protected成员
C c = new C();
// System.out.println(c.b); // 编译报错:不能访问其他子类对象的protected成员
}
}
/**
* 子类2:com.demo2包,继承Base
*/
class C extends Base {}
核心结论 :protected的可见性是 **"子类可见"**,而非 "子类的对象可见",不同包的子类之间不能互相访问 protected 成员。
9.3 访问修饰符的使用原则
为了保证类的封装性,使用修饰符时遵循 **"宁严勿宽"** 的原则:
- 仅类内部使用的成员,用
private; - 同一包内的类使用的成员,用
default; - 不同包的子类使用的成员,用
protected; - 所有类都需要使用的成员,用
public; - 成员变量优先用
private(封装),通过get/set方法访问;成员方法根据实际使用范围选择修饰符。
十、Java 的继承方式与 final 关键字
10.1 Java 支持的继承方式
Java 是单继承 语言,一个子类只能有一个直接父类,但支持多层继承 (子类继承父类,父类再继承祖父类),不支持多继承(一个子类继承多个父类),避免类层次结构混乱。
| 继承方式 | 示例 | 是否支持 |
|---|---|---|
| 单继承 | class B extends A{} |
✅ |
| 多层继承 | class C extends B、class B extends A |
✅ |
| 多继承 | class C extends A,B{} |
❌ |
为什么不支持多继承? 如果多个父类有同名的方法,子类继承后会出现方法冲突,无法确定调用哪个父类的方法,导致程序歧义。
10.2 final 关键字:限制继承
final关键字可修饰变量 、方法 、类,其中修饰类和方法时,与继承直接相关:
- 修饰类 :表示该类不能被继承 ,称为 "最终类";
- 示例:
public final class String{}(Java 的 String 类就是 final 类,不能被继承);
- 示例:
- 修饰方法 :表示该方法不能被子类重写(后序多态章节会详细讲解);
- 修饰变量 :表示该变量是常量,值不能被修改。
final 修饰类的示例(禁止继承)
java
/**
* final修饰的类:不能被继承
*/
public final class Animal {
public void eat() {
System.out.println("动物吃饭");
}
}
/**
* 编译报错:Cannot inherit from final 'Animal'
*/
public class Dog extends Animal { // 错误!final类不能被继承
public void bark() {
System.out.println("狗叫");
}
}
10.3 继承的层次建议
实际开发中,不建议继承层次超过 3 层 ,如果继承层次太深,会导致类的耦合性过高,代码可读性和维护性大幅下降。若层次过深,建议考虑重构代码(比如使用组合)。
十一、继承与组合:如何选择?
除了继承,组合也是 Java 中实现代码复用的重要方式,两者的核心区别在于类之间的关系:
- 继承 :表示is-a关系(狗是动物、学生是人);
- 组合 :表示has-a关系(汽车有发动机、电脑有 CPU)。
11.1 组合的实现方式
组合无需特殊关键字,只需将一个类的对象作为另一个类的成员变量,通过该对象访问其成员,实现代码复用。
11.2 继承与组合的对比示例
以汽车为例,汽车由发动机、轮胎、车载系统组成(has-a),使用组合实现;奔驰是汽车的一种(is-a),使用继承实现。
java
/**
* 轮胎类:组件类
*/
class Tire {
public void run() {
System.out.println("轮胎转动,汽车前进");
}
}
/**
* 发动机类:组件类
*/
class Engine {
public void start() {
System.out.println("发动机启动,提供动力");
}
}
/**
* 车载系统类:组件类
*/
class VehicleSystem {
public void open() {
System.out.println("车载系统启动,支持导航/音乐");
}
}
/**
* 汽车类:通过组合复用组件类的功能
*/
class Car {
// 组合:将组件类的对象作为成员变量
private Tire tire = new Tire();
private Engine engine = new Engine();
private VehicleSystem vs = new VehicleSystem();
// 汽车的启动方法:调用组件类的方法
public void startCar() {
engine.start();
tire.run();
vs.open();
System.out.println("汽车成功启动!");
}
}
/**
* 奔驰类:通过继承复用汽车类的功能
*/
class Benz extends Car {
// 奔驰的特有方法
public void luxury() {
System.out.println("奔驰:高端内饰,智能驾驶");
}
}
/**
* 测试类
*/
public class TestCombine {
public static void main(String[] args) {
Benz benz = new Benz();
benz.startCar(); // 继承自Car的方法
benz.luxury(); // 自身特有方法
}
}
运行结果:
发动机启动,提供动力
轮胎转动,汽车前进
车载系统启动,支持导航/音乐
汽车成功启动!
奔驰:高端内饰,智能驾驶
11.3 继承与组合的选择原则
核心建议:能用组合,尽量用组合,原因如下:
- 组合的耦合性更低:组合是 "使用关系",子类仅使用组件类的功能,不依赖其内部实现;继承是 "父子关系",子类依赖父类的实现,耦合性高;
- 组合更灵活:可随时替换组件类的实现,而继承的父类一旦确定,无法轻易修改;
- 组合避免单继承限制:Java 不支持多继承,但可通过组合多个组件类,实现多 "功能" 的复用;
- 继承的适用场景 :只有当类之间确实是is-a的关系时,才使用继承(比如学生是人、狗是动物)。
十二、Java 继承高频面试题解析
结合本文内容,整理几道 Java 继承的高频面试题,帮你巩固知识点:
题 1:以下关于 Java 继承的说法,正确的是?(D)
A. 一个类可以同时继承多个类B. 子类可以直接访问父类的私有 (private) 成员C. 使用 final 关键字修饰的类可以被继承D. 子类可以重写 (override) 父类的方法
解析:A 错误,Java 不支持多继承;B 错误,私有成员被封装,子类无法直接访问;C 错误,final 类不能被继承;D 正确,子类可重写父类的非 final 方法。
题 2:以下关于 Java 继承的说法,错误的是?(C)
A. 子类可以通过 super 关键字调用父类的构造方法B. 所有的 Java 类都直接或间接继承自 java.lang.Object 类C. 构造方法可以被子类继承D. 使用 protected 修饰的成员可以被子类访问
解析:A 正确,super (...) 可调用父类构造方法;B 正确,Object 是 Java 所有类的根父类;C 错误,构造方法不能被继承,子类只能通过 super 调用;D 正确,protected 的核心作用是让子类访问。
题 3:子类构造方法中,super (...) 的使用要求是什么?
答案:① 必须是子类构造方法的第一行代码;② 只能出现一次;③ 不能和 this (...) 同时使用;④ 若父类无无参构造,子类必须显式调用 super (...)。
题 4:继承体系中,静态代码块、实例代码块、构造方法的执行顺序是什么?
答案:父类静态代码块 → 子类静态代码块 → 父类实例代码块 → 父类构造方法 → 子类实例代码块 → 子类构造方法;静态代码块仅执行一次。
十三、总结
Java 继承是面向对象编程的核心机制,其核心价值是抽取共性、实现代码复用,同时为多态奠定基础。本文从实际需求出发,讲解了继承的语法、父类成员访问规则、super 关键字、子类构造方法、初始化顺序、访问修饰符、final 关键字,以及与组合的区别,核心知识点可总结为以下几点:
- 继承使用
extends关键字,Java 支持单继承和多层继承,不支持多继承; - 子类继承父类的非私有成员,必须新增自身特有成员,否则无继承意义;
- 父类成员访问遵循就近原则,同名成员需用 super 关键字访问;
- 子类构造时先构造父类,super (...) 必须是子类构造方法的第一行;
- 继承体系的初始化顺序:静态代码块(父→子)→ 实例代码块 + 构造方法(父→子);
- protected 为继承设计,允许不同包的子类访问父类成员;
- final 修饰的类不能被继承,修饰的方法不能被重写;
- 继承是 is-a 关系,组合是 has-a 关系,实际开发中优先使用组合。
掌握继承的核心知识点,不仅能写出更简洁、易维护的代码,还能为后续学习多态打下坚实的基础。建议结合本文的代码示例亲自运行、调试,加深对知识点的理解。