本节摘要:接口是纯契约类型:方法默认公开抽象、字段默认公开静态常量,一个类 implements 多个接口即可"持有多张证",弥补单继承的限制。default 方法让接口自带可选默认实现,static 方法提供工具入口,函数式接口只允许一个抽象方法。本节用"会飞、会游泳"的横切能力演示多实现组合,并讲清 default 方法的演化背景与调用规则。
第 2 章末尾留了问题:鸟、飞机、超人都会飞,但它们在族谱上毫无关系——鸟属于动物家族、飞机属于机械家族、超人属于人类家族。把 fly 塞进某个共同祖先显然荒谬。这类横切能力(cross-cutting ability)需要另一种登记物:接口。接口只声明"会什么",不问"你是谁家的":
interface Flyable { // 资格证:只写条目 不写做法 void fly(); // 隐式 public abstract } interface Swimmable { void swim(); } class Bird extends Animal implements Flyable { // 一本族谱 加 一张证 @Override public void fly() { System.out.println("鸟类:扑翼飞行"); } } class Plane extends Machine implements Flyable { // 同一张证 不同家族都能持 @Override public void fly() { System.out.println("飞机:引擎推力飞行"); } } class Duck extends Animal implements Flyable, Swimmable { // 同时持两张证 @Override public void fly() { System.out.println("鸭子:短距滑翔"); } @Override public void swim() { System.out.println("鸭子:水面浮游"); } }
Animal 与 Machine 是各自家族的抽象或具体类,细节略去。看 Duck 的登记栏:一本族谱(extends Animal)加两张证书(implements Flyable, Swimmable)。单继承保家族、多实现扩能力,这就是 Java 类型系统的分工。接口引用与父类引用一样能接住多态(2.2 节机制照常生效):
public class CertDemo { public static void main(String[] args) { Flyable f1 = new Bird(); Flyable f2 = new Plane(); f1.fly(); // 输出:鸟类:扑翼飞行 f2.fly(); // 输出:飞机:引擎推力飞行 Duck d = new Duck(); Flyable fd = d; Swimmable sd = d; // 同一对象 被两种证书引用 fd.fly(); // 输出:鸭子:短距滑翔 sd.swim(); // 输出:鸭子:水面浮游 } }
证书换的是"视野":fd 只看得见 fly,sd 只看得见 swim,对象还是同一份。这与父类引用收窄视野是同一个机制。
接口体内每一行都被隐式修饰,这几条必须刻进肌肉记忆:方法默认 public abstract(写不写都一样,实现时必须显式写 public,漏写会编译报错"试图指定更弱的访问权限");字段默认 public static final——接口里写 int MAX = 100; 实际是常量,接口因此不能有实例状态,这正是它与抽象类的本质区别之一;default 方法自带方法体,实现类不写也能用、不满意可以重写;static 方法属于接口本身,用接口名调用。
default 方法的来历值得讲一句:它不是设计出来的语法糖,是被现实逼出来的。集合框架改造时需要给 Collection 接口加一批新方法(如流式操作入口),若无 default,全球所有实现类一夜之间全部编译失败——给已发布的接口加方法曾是"不可能的事"。default 让接口可以安全演化:新方法给个默认实现,老实现类不动也能过编译。代价是接口从此沾上了一点"实现"的味道,与抽象类的界线略微软化。
interface Auditable { int LEVEL = 3; // 隐式 public static final 常量 void mark(); // 抽象条目:必须实现 default void report() { // 默认做法:可不实现 System.out.println("默认审计:记录操作日志 级别 " + LEVEL); } static void check(Auditable a) { // 静态工具:接口名直接调 System.out.println("开始核查"); a.mark(); a.report(); } } class Payment implements Auditable { @Override public void mark() { System.out.println("支付操作:标记为需审计"); } // report 不重写 用默认做法 } public class DefaultDemo { public static void main(String[] args) { Auditable p = new Payment(); Auditable.check(p); // 输出:开始核查 / 支付操作:标记为需审计 / 默认审计:记录操作日志 级别 3 System.out.println("审计级别常量:" + Auditable.LEVEL); // 输出:审计级别常量:3 } }

接口可以继承接口,且可以一次继承多个——Java 里唯一允许多继承的位置,集合框架的 List 接口就是这么同时继承多份契约的(第 5 章会见到)。类与接口之间则是 implements;抽象类也能实现接口,把空栏继续往下传或自己补一部分。
完整案例走一遍多实现:背景:文件同步器要支持多种目标(本地磁盘、远端存储),都需要"连接、写入、断开"三步,另外都要能报告进度。操作:把三步做成接口的方法条目,进度做成另一个接口的 default 方法,实现类按目标各写各的三步、进度用默认实现:
interface Target { void connect(String where); void write(byte[] data); void disconnect(); } interface Progressive { default void showProgress(long done, long total) { // 默认进度条 所有实现免费获得 System.out.println("进度 " + done * 100 / total + " %"); } } class LocalTarget implements Target, Progressive { // 本地目标 持两张证 public void connect(String where) { System.out.println("挂载本地目录 " + where); } public void write(byte[] data) { System.out.println("写入 " + data.length + " 字节"); } public void disconnect() { System.out.println("卸载目录"); } } class RemoteTarget implements Target, Progressive { // 远端目标 同样两张证 public void connect(String where) { System.out.println("建立远端连接 " + where); } public void write(byte[] data) { System.out.println("分块上传 " + data.length + " 字节"); } public void disconnect() { System.out.println("断开远端连接"); } } public class SyncDemo { static void sync(Target t, String where) { // 调用方只认证书 t.connect(where); t.write("hello".getBytes()); t.disconnect(); } public static void main(String[] args) { sync(new LocalTarget(), "备份盘"); // 输出挂载 写入 卸载三行 sync(new RemoteTarget(), "云端"); // 输出连接 上传 断开三行 new RemoteTarget().showProgress(3, 4); // 输出:进度 75 % } }
结果:sync 方法只依赖 Target 证书,新增"对象存储"目标时一行不改。解读:接口把"能力约定"从家族中剥离,调用方依赖证书而非家族,扩展点自然打开。变式:若三步之间有严格的先后校验(连接失败不许写),流程骨架该由抽象类承担——接口管"会不会",抽象类管"按什么顺序做",两者经常搭配:抽象骨架类实现接口、填通用步骤、留关键空栏。
抽象类与接口各执一端,真实设计里怎么选?下一节给出一套可操作的选型标准,并用 JDK 集合框架的双层设计做实案复盘。