写 Java 时经常会遇到两个"不能直接 new"的东西:接口和抽象类。它们都用来定义规范,但分工不同。这篇把两者的用法和区别讲清楚。
抽象类:只给骨架,不给全部实现
用 abstract 修饰的类就是抽象类,它不能被实例化,只能被继承。里面可以放普通方法,也可以放没有方法体的抽象方法。
abstract class Shape {
String name;
Shape(String name) { // 抽象类也能有构造函数
this.name = name;
}
abstract double area(); // 抽象方法:子类必须实现
void describe() { // 普通方法:子类直接用
System.out.println(name + " 面积是 " + area());
}
}
class Circle extends Shape {
double r;
Circle(double r) {
super("圆形");
this.r = r;
}
@Override
double area() {
return 3.14 * r * r;
}
}
public class Main {
public static void main(String[] args) {
Shape s = new Circle(2);
s.describe(); // 输出:圆形 面积是 12.56
}
}
注意 new Shape(...) 是编译不过的,但 Shape s = new Circle(2) 可以——变量类型是抽象类,实际对象是子类,这就是多态的常见用法。
接口:一份"能力合同"
接口用 interface 定义,里面的方法默认是公开的抽象方法。一个类用 implements 签下这份合同,就必须把合同里的方法全部实现。
interface Flyable {
void fly();
}
interface Swimmable {
void swim();
}
class Duck implements Flyable, Swimmable {
public void fly() { System.out.println("鸭子扑腾着飞了"); }
public void swim() { System.out.println("鸭子在水里游"); }
}
public class Main {
public static void main(String[] args) {
Duck d = new Duck();
d.fly(); // 输出:鸭子扑腾着飞了
d.swim(); // 输出:鸭子在水里游
}
}
接口还可以当类型用:Flyable f = new Duck(); 之后 f.fly() 没问题,但 f.swim() 编译不过,因为 Flyable 这份合同只承诺了 fly。
接口里的默认方法
Java 8 之后接口可以带默认实现,实现类不写它也有。
interface Greeter {
default void hello() {
System.out.println("你好");
}
}
class Robot implements Greeter { } // 什么都不用写
// new Robot().hello(); 输出:你好
两者的区别
- 抽象类描述"是什么"(Shape 是形状),接口描述"能做什么"(能飞、能游泳)
- 类只能继承一个抽象类,但可以实现多个接口
- 抽象类能有字段、构造函数和各种访问级别的方法;接口里主要是常量和抽象方法(外加 default 方法)
- 拿不准的时候优先用接口,它更灵活
小结
- 抽象类不能实例化,用 abstract 定义抽象方法逼子类实现,同时可以复用普通代码
- 接口是一份能力合同,implements 之后必须实现全部抽象方法
- Java 8 起接口支持 default 方法,实现类可以直接继承这份默认行为
- 记住口诀:单继承、多实现
问一下,接口的 default 方法要是跟抽象类里的普通方法签名撞了,实现类继承的是哪个?单继承多实现这口诀倒是好记