Java 抽象类与接口

当父类只想定义"做什么",不想写"怎么做"时,就用抽象类接口。它们是多态的"基石",也是 Java 工程化的核心工具——所有 Java 框架的设计都离不开接口。本章彻底讲清两者的区别与选择。

1. 抽象类(abstract class)

abstract 修饰的类叫抽象类,特点是:

// 抽象类:用 abstract 修饰,不能 new(不能直接实例化)
abstract class Shape {
    protected String name;

    public Shape(String name) {
        this.name = name;
    }

    // 普通方法:可以有方法体
    public String describe() {
        return name + " 面积 = " + area();
    }

    // 抽象方法:只有声明,没有方法体
    // 子类必须实现(除非子类也是抽象的)
    public abstract double area();
}

// 圆形:必须实现 area()
class Circle extends Shape {
    private double r;
    public Circle(double r) {
        super("圆形");
        this.r = r;
    }
    @Override
    public double area() { return Math.PI * r * r; }
}

class Rectangle extends Shape {
    private double w, h;
    public Rectangle(double w, double h) {
        super("矩形");
        this.w = w;
        this.h = h;
    }
    @Override
    public double area() { return w * h; }
}

public class AbstractDemo {
    public static void main(String[] args) {
        // Shape s = new Shape("x");    // ❌ 抽象类不能 new
        Shape s1 = new Circle(2);
        Shape s2 = new Rectangle(3, 4);
        System.out.println(s1.describe());   // 圆形 面积 = 12.566...
        System.out.println(s2.describe());   // 矩形 面积 = 12.0
    }
}

抽象类的价值:作为"模板",强制子类实现某些方法(抽象方法),同时提供公共代码(普通方法/字段),避免重复。Shape 定义 area() 抽象方法,所有子类必须实现——这保证了"任何 Shape 都能算面积"。

2. 接口(interface)

接口是更彻底的抽象——Java 8 之前只能有抽象方法和常量。Java 8+ 放宽了,可以有默认方法和静态方法。

// 接口:用 interface 定义,方法默认 public abstract
interface Flyable {
    void fly();          // 抽象方法(隐式 public abstract)
}

interface Swimmer {
    void swim();
}

// 一个类可以实现多个接口(实现"多继承"效果)
// 用逗号分隔多个接口
class Duck implements Flyable, Swimmer {
    @Override
    public void fly() { System.out.println("鸭子会飞"); }

    @Override
    public void swim() { System.out.println("鸭子会游泳"); }
}

public class InterfaceDemo {
    public static void main(String[] args) {
        Duck d = new Duck();

        // 接口引用也能指向实现类对象(类似向上转型)
        Flyable f = d;
        f.fly();                  // 鸭子会飞

        Swimmer s = d;
        s.swim();                 // 鸭子会游泳
    }
}

接口的关键特性:

3. Java 8+ 默认方法与静态方法

Java 8 给接口带来两个重要特性,解决了"接口升级"难题:

// Java 8+ 接口可以有"默认方法"和"静态方法"
interface Calculator {
    // 抽象方法
    int calculate(int a, int b);

    // 默认方法:用 default 修饰,有方法体
    // 解决"接口升级"问题——给老接口加方法,不用改所有实现类
    default int doubleCalc(int a, int b) {
        return calculate(a, b) * 2;
    }

    // 静态方法:属于接口本身,直接 接口名.方法() 调用
    static Calculator getAdder() {
        return (a, b) -> a + b;       // lambda 表达式
    }
}

// 实现类只需要实现抽象方法,默认方法自动继承
class Adder implements Calculator {
    @Override
    public int calculate(int a, int b) { return a + b; }
}

public class DefaultMethodDemo {
    public static void main(String[] args) {
        Calculator adder = new Adder();
        System.out.println(adder.calculate(3, 5));        // 8
        System.out.println(adder.doubleCalc(3, 5));       // 16(默认方法)

        // 调用接口的静态方法
        Calculator lambda = Calculator.getAdder();
        System.out.println(lambda.calculate(10, 20));     // 30
    }
}

默认方法的价值:给老接口加方法,不用改所有实现类。Java 8 的 Collection.stream()Iterable.forEach() 都是默认方法,这样所有 Collection 的实现类都自动获得了 stream 能力。静态方法用于放工具函数(如 Comparator.comparing())。

4. 函数式接口与 Lambda

函数式接口 = 有且只有一个抽象方法的接口。这种接口可以用 Lambda 表达式(Java 8+) 简洁地创建实例:

// 函数式接口:有且只有一个抽象方法的接口
// 可以用 @FunctionalInterface 让编译器帮你检查
@FunctionalInterface
interface Greeter {
    void greet(String name);

    // void another();     // ❌ 加这个就不再是函数式接口
    // Object 重写的方法不算(toString/equals)
}

public class FunctionalDemo {
    public static void main(String[] args) {
        // 1. 传统匿名内部类(啰嗦)
        Greeter g1 = new Greeter() {
            @Override
            public void greet(String name) {
                System.out.println("Hi, " + name);
            }
        };

        // 2. Lambda 表达式(Java 8+,简洁)
        // 编译器看到 Greeter 只有一个抽象方法,自动匹配
        Greeter g2 = (name) -> System.out.println("Hello, " + name);
        Greeter g3 = name -> System.out.println("你好, " + name);   // 单参数可省括号

        g1.greet("小明");    // Hi, 小明
        g2.greet("小明");    // Hello, 小明
        g3.greet("小明");    // 你好, 小明

        // Java 内置的函数式接口(在 java.util.function 包)
        // Runnable、Comparator、Predicate、Function、Consumer、Supplier...
        Runnable r = () -> System.out.println("running");
        new Thread(r).start();
    }
}

函数式接口是 Java 函数式编程的基础。Java 内置了一批常用函数式接口(在 java.util.function 包),不必自己写:

5. 抽象类 vs 接口:怎么选?

这是新手最常问的问题。看下表对比:

特性抽象类接口
继承数单继承(一个 extends)多实现(多个 implements)
字段任意类型只能 public static final 常量
方法任意(抽象+普通)抽象+默认+静态(Java 8+)
构造方法有(给子类调)没有
访问修饰符任意默认 public

选择原则:

6. 实战:组合使用

// 抽象类:适合"模板"——共享代码 + 共享字段
abstract class Vehicle {
    protected String name;

    public Vehicle(String name) { this.name = name; }

    // 公共逻辑,所有子类复用
    public void startEngine() {
        System.out.println(name + " 启动引擎");
    }

    public abstract int getWheelCount();   // 子类各自实现
}

// 接口:适合"能力"——可以做什么
interface Electric {
    void charge();
}

// 一个类:继承一个抽象类 + 实现多个接口
class ElectricCar extends Vehicle implements Electric {
    public ElectricCar() { super("电车"); }

    @Override public int getWheelCount() { return 4; }

    @Override public void charge() { System.out.println("充电中..."); }
}

实际工程中,抽象类和接口经常配合使用:抽象类提供基础实现,接口定义额外能力。Java 类库中 AbstractList(抽象类)+ List(接口)+ ArrayList(具体类)就是这种模式。

小结

这一章你掌握了 Java 抽象类与接口:abstract class 强制实现 + 共享代码,interface 定义能力 + 多实现,Java 8+ 默认方法解决接口升级,函数式接口拥抱 Lambda。下一篇我们看 Java 工程师的看家本领——集合框架。

← 上一篇 Java 多态

下一篇 Java 集合框架

✈️💬