Jakob Jenkov 2015-03-09
我经常被问到的问题是:Java 中接口(Interface)和抽象类(Abstract Class)有什么区别?什么时候该用哪一个?由于我已经多次通过电子邮件回答这个问题,因此决定撰写这篇关于 Java 接口与抽象类对比的教程。
接口的作用
Java 接口用于将某个组件的接口与其实现解耦。换句话说,使用该接口的类可以独立于实现该接口的类。这样,你就可以在不修改使用方代码的前提下,更换接口的具体实现。
抽象类的作用
抽象类通常被用作子类扩展的基础类。某些编程语言使用抽象类来实现多态性,并分离接口与实现;但在 Java 中,这项任务是由接口完成的。
请记住:一个 Java 类只能有一个直接父类,但可以实现多个接口。因此,如果一个类已经继承了另一个类(即已有超类),它就无法再继承另一个抽象类,但仍然可以实现接口。从这个角度看,接口是一种更灵活的机制,用于暴露公共行为。
使用建议
- 如果你需要将接口与其实现分离,请使用接口。
- 如果你还需要提供一个基础类或接口的默认实现,则可以添加一个抽象类(或普通类)来实现该接口。
下图展示了一个典型结构:
graph
Class --> Interface
AbstractClass --o Interface
Subclass -.extends.-> AbstractClass
style Class fill:#a0d3e8,stroke:#333,stroke-width:2px
style Interface fill:#d5f5e3,stroke:#333,stroke-width:2px
style AbstractClass fill:#ffe4b5,stroke:#f4a460,stroke-width:2px,stroke-dasharray: 5 5
style Subclass fill:#ffe4b5,stroke:#f4a460,stroke-width:2px
蓝色类引用接口。抽象类实现了该接口,而子类继承自抽象类。
代码示例
以下是来自 Java 抽象类 教程中的代码示例,但额外添加了一个由抽象基类实现的接口,使其与上图结构一致。
第一步:定义接口
public interface URLProcessor {
public void process(URL url) throws IOException;
}
第二步:定义抽象基类(实现接口)
public abstract class URLProcessorBase implements URLProcessor {
public void process(URL url) throws IOException {
URLConnection urlConnection = url.openConnection();
InputStream input = urlConnection.getInputStream();
try {
processURLData(input);
} finally {
input.close();
}
}
protected abstract void processURLData(InputStream input) throws IOException;
}
第三步:定义具体子类(继承抽象类)
public class URLProcessorImpl extends URLProcessorBase {
@Override
protected void processURLData(InputStream input) throws IOException {
int data = input.read();
while(data != -1){
System.out.println((char) data);
data = input.read();
}
}
}
第四步:使用接口作为变量类型(实际创建的是子类实例)
URLProcessor urlProcessor = new URLProcessorImpl();
urlProcessor.process(new URL("https://mianshima.com"));
灵活性优势
同时使用接口和抽象基类可以让你的代码更加灵活:
- 对于简单的 URL 处理器,可以直接继承
URLProcessorBase抽象类; - 如果需要更复杂或不同的行为,也可以选择直接实现
URLProcessor接口,而不继承URLProcessorBase。