为什么不支持多类继承
本课属于 Coddy Java 学习旅程的“面向对象编程”单元。第 26 课,共 87 课。
Java 有意禁止一个类继承多个类。这个限制是为了解决一个称为菱形问题的问题。
想象一下,如果 Java 允许多重继承。考虑下面这个假设场景:
// 这段代码在 Java 中无效
public class A {
public void greet() {
System.out.println("Hello from A");
}
}
public class B extends A {
@Override
public void greet() {
System.out.println("Hello from B");
}
}
public class C extends A {
@Override
public void greet() {
System.out.println("Hello from C");
}
}
// Java 中不允许这样写!
public class D extends B, C { }如果你在 D 对象上调用了 greet(),应该运行哪个版本?来自 B 的还是来自 C 的?这种歧义就是钻石问题,它因继承关系图形成的钻石形状而得名。
Java 的解决方案很简单:只允许类进行单继承。不过,Java 提供了另一种选择。一个类可以实现多个接口,你将在后面的章节中学习相关内容。
接口以不同的方式解决菱形问题,让你在避免歧义的同时拥有灵活性。
// 这在 Java 中确实是有效的
public class Dog extends Animal implements Runnable, Comparable<Dog> {
// 可以实现多个接口
}这种设计选择让 Java 的继承模型保持简洁且可预测,同时仍然可以通过接口强大地复用代码。
挑战
简单我们来构建一个音乐播放器系统,展示 Java 的单继承规则,并说明在这一限制下,你仍然可以设计出灵活的结构。
想象一下,你想创建一个智能音箱,同时具备音乐播放器和语音助手的功能。由于 Java 不允许继承多个类,你需要仔细设计类的层次结构,使用单继承来实现。
你将创建 4 个文件来组织代码:
AudioDevice.java:创建所有音频播放设备共用的基类。它应该包含:- 一个用于存储
deviceName的私有字段(String) - 一个接收设备名称的构造函数
- 一个返回名称的方法
getDeviceName() - 一个输出以下内容的方法
playSound():[deviceName]: Playing audio
- 一个用于存储
MusicPlayer.java:创建一个继承 AudioDevice 并添加音乐专属功能的类:- 一个用于存储
currentSong的私有字段(String) - 一个接收设备名称并使用
super(deviceName)的构造函数 - 一个设置当前歌曲的方法
setSong(String song) - 重写
playSound(),输出:[deviceName]: Now playing - [currentSong]
- 一个用于存储
SmartSpeaker.java:创建一个继承 MusicPlayer 的类(不要同时继承 MusicPlayer 和某个 VoiceAssistant 类,那样就属于多继承了!)。智能音箱在音乐播放器的基础上添加语音功能:- 一个用于存储
assistantName的私有字段(String) - 一个接收设备名称和助手名称,并使用
super(deviceName)的构造函数 - 一个输出以下内容的方法
voiceCommand(String command):[assistantName]: Processing "[command]" - 一个输出以下内容的方法
getFullInfo():[deviceName] with [assistantName] assistant
- 一个用于存储
Main.java:展示多级继承链。你会收到 3 个输入:设备名称(String)、助手名称(String)和歌曲名称(String)。创建一个 SmartSpeaker,设置歌曲,然后按顺序调用以下方法:getFullInfo()playSound()voiceCommand("skip to next")
你会收到 3 个输入:设备名称、助手名称和歌曲名称。
注意,SmartSpeaker 是通过继承 MusicPlayer 来获得音乐功能的,形成一条继承链(AudioDevice -> MusicPlayer -> SmartSpeaker),而不是尝试继承多个互不相关的类。这正是 Java 保持继承结构清晰明确的方式!
亲手试试
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 读取输入
String deviceName = scanner.nextLine();
String assistantName = scanner.nextLine();
String songName = scanner.nextLine();
// TODO: 使用 deviceName 和 assistantName 创建 SmartSpeaker 对象
// TODO: 使用 setSong() 设置歌曲
// TODO: 调用 getFullInfo()
// TODO: 调用 playSound()
// TODO: 调用 voiceCommand("skip to next")
}
}
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: Java 在线编译器