写 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 方法,实现类可以直接继承这份默认行为
  • 记住口诀:单继承、多实现