讲解
接口(interface)是一份纯契约:只声明「能做什么」,不规定「怎么做」。interface Flyable { void fly(); } 表示「会飞」这个能力,任何类用 implements 认领这份契约,就必须实现里面的方法。与抽象类最大的两点不同:一个类可以实现任意多个接口(突破了单继承的限制),接口不能持有实例字段(Java 8 之前方法也全抽象,之后允许 default 默认方法和 static 方法)。
default 方法让接口可以携带一份「默认实现」:实现类不重写也能直接用,想定制就重写。这个特性是为了让老接口能安全地增加新方法——如果加的是抽象方法,所有现存实现类都会编译失败,default 方法则向后兼容。接口里的字段默认是 public static final 常量;接口里的方法默认是 public abstract,修饰符可以不写。
接口的正确打开方式是「面向接口编程」:声明变量和参数时用接口类型而不是具体类——List list = new ArrayList<>();(下一章的集合就是这个模式)。好处是更换实现时(比如换成 LinkedList)只需改 new 那一处,其余代码因为只依赖接口承诺的方法而纹丝不动。接口表达的是「有一个能力」(can-do),和继承的「是一种」(is-a)分工明确:类层次用继承,能力横切用接口。
示例
定义接口并实现:契约强制实现类提供方法:
interface Drawable {
void draw();
}
class Circle implements Drawable {
@Override
public void draw() {
System.out.println("画一个圆 ○");
}
}
class Triangle implements Drawable {
@Override
public void draw() {
System.out.println("画一个三角形 △");
}
}
public class Main {
public static void main(String[] args) {
Drawable[] shapes = {new Circle(), new Triangle()};
for (Drawable d : shapes) {
d.draw();
}
}
}
多实现与 default 方法:一只鸭子同时认领两份契约:
interface Flyable {
void fly();
default String category() {
return "会飞的";
}
}
interface Swimmable {
void swim();
}
class Duck implements Flyable, Swimmable {
@Override
public void fly() {
System.out.println("鸭子扑腾着飞了一小段");
}
@Override
public void swim() {
System.out.println("鸭子悠闲地游水");
}
}
public class Main {
public static void main(String[] args) {
Duck duck = new Duck();
duck.fly();
duck.swim();
System.out.println("分类:" + duck.category());
}
}
常见坑
- 实现类漏掉 public:接口方法默认 public,实现时若省略 public(写成默认包级权限)等于缩小了可见性,直接编译错误。实现接口方法一律写 public。
- new 一个接口:接口和抽象类一样不能实例化,List x = new List<>() 行不通,要 new 的是具体实现类。
- 多个接口的 default 方法撞名:两个接口各有一个同名 default 方法,实现类必须重写该方法(可用 接口名.super.方法名() 指定复用哪一份),否则编译失败。
- 接口里想存状态:接口不能有实例字段,需要共享数据状态时用抽象类,或把状态放进实现类。接口管「能力」,不管「家底」。
小结
接口是纯能力契约,多实现、无状态,default 方法提供兼容的默认实现;变量声明面向接口类型。下一节学习程序出错时的应对机制:异常处理。