HOWTO · Java
在 Java 中扩展两个类
了解 Java 类为什么只能扩展一个类,以及接口和组合如何结合多种类型的行为。
本页内容
Java 类不能同时继承两个类。每个类只能有一个直接超类,因此 class Child extends First, Second 这样的声明不是有效的 Java 语法。若要组合多个类型的行为,可以继承一个类并实现任意数量的接口,也可以持有其他对象并通过组合委托工作。
为什么 Java 类不能继承两个类
Java 类采用单继承:每个类在 extends 后最多只能指定一个超类。这样,每个对象只有一条类继承链;如果两个父类定义了同一个成员,也就不会产生该使用哪个超类实现或状态的歧义。编译器会拒绝列出两个超类的类声明;这是编译时限制,并非程序运行时才会发生的错误。
下面是刻意写出的无效 Java 语法,无法通过编译:class Child extends First, Second {}。如果新类确实是某个类型的专门形式,就使用一个超类。下一节将介绍如何通过接口契约声明额外的能力。
继承一个超类并实现多个接口
一个类可以继承一个类并实现多个接口。超类可以提供共享状态或实现,而每个接口则声明该类承诺提供的一项能力。Java 关键字的用法很重要:类通过 extends 指定超类,通过 implements 实现接口。接口本身可以继承多个接口,但这并不意味着类可以拥有多个类超类。
下面的完整示例定义了两个接口契约,并在继承一个超类的同时实现这两个接口:
public class CatalogItem extends Record implements Printable, Identifiable {
CatalogItem(String name) {
super(name);
}
@Override
public void printLabel() {
System.out.println(name());
}
@Override
public String id() {
return "item-" + name().toLowerCase();
}
public static void main(String[] args) {
CatalogItem item = new CatalogItem("Book");
item.printLabel();
System.out.println(item.id());
}
}
interface Printable {
void printLabel();
}
interface Identifiable {
String id();
}
class Record {
private final String name;
Record(String name) {
this.name = name;
}
String name() {
return name;
}
}
Book
item-book
CatalogItem 继承了 Record 的 name() 实现,并提供两个接口所要求的方法。当互不相关的类需要共享同一种类型或能力契约时,接口很有用。但接口不会像第二个超类那样自动提供状态或类实现。
通过组合组合多个类的行为
组合通过“拥有”(has-a)关系来组合对象:一个类保存协作者对象的引用,并调用它们的方法。当这些行为来自各自有用的类,或者需要能够替换协作者时,这种方式很合适。包含协作者的类可以控制对外提供哪些操作,只委托调用方所需的工作。
下面的 SmartDevice 包含一个 PowerController 和一个 MessageSender,并将操作委托给它们:
public class SmartDevice {
private final PowerController power = new PowerController();
private final MessageSender sender = new MessageSender();
void start() {
power.powerOn();
}
void sendMessage(String message) {
sender.send(message);
}
public static void main(String[] args) {
SmartDevice device = new SmartDevice();
device.start();
device.sendMessage("Ready");
}
}
class PowerController {
void powerOn() {
System.out.println("Power on");
}
}
class MessageSender {
void send(String message) {
System.out.println("Sending: " + message);
}
}
Power on
Sending: Ready
SmartDevice 并不会变成 PowerController 或 MessageSender;它只是使用这两个对象。在更大的程序中,可以通过构造函数传入协作者,也可以用接口表示协作者。这样更容易替换实现,也便于独立测试包含协作者的类。
如何选择合适的代码复用方式
当新类型确实是某个超类的专门形式,并且需要继承其实现时,选择类继承。如果某个类型需要承诺提供与其他类共享的能力,就添加接口。当行为需要由多个部分组合而成、通过委托调用或能够独立更改时,就使用组合。
让每个类专注于明确的职责,并且只公开调用方需要的操作。优先采用小而精的接口契约,避免重复编写本应由共享协作者提供的行为。这样可以让对象之间的关系更容易理解、测试,并随需求变化而调整。简而言之,Java 不允许一个类继承两个类;一个超类、多个接口契约和组合协作者足以满足常见的复用需求。