V
Vel·ToolKit
简洁 · 高效 · 即开即用
ZH
第 9 章 / 共 20 章

抽象类与接口

abstract、interface、default 方法、函数式接口家族

抽象类(abstract class)

用 abstract 修饰的类不能直接实例化,必须被继承。常用于"已经有部分实现、剩余细节交给子类"的场景。抽象类可以有字段、构造器、普通方法、抽象方法。

// AbstractDemo.java
public class AbstractDemo {
    public static void main(String[] args) {
        Shape s = new Circle(2);
        s.describe();
    }
}

abstract class Shape {
    abstract double area();           // 抽象方法:只声明不实现

    void describe() {                  // 普通方法
        System.out.println("area = " + area());
    }
}

class Circle extends Shape {
    double r;
    Circle(double r) { this.r = r; }
    @Override double area() { return Math.PI * r * r; }
}

接口(interface)

接口定义"能做什么",不关心"怎么做"。类用 implements 声明实现某个接口;接口里默认所有方法 public abstract、所有字段 public static final,可以省略写。

// InterfaceDemo.java
public class InterfaceDemo {
    public static void main(String[] args) {
        Greeter g = new EnglishGreeter();
        g.greet("Alice");
    }
}

interface Greeter {
    void greet(String name);          // 隐含 public abstract
}

class EnglishGreeter implements Greeter {
    @Override
    public void greet(String name) {
        System.out.println("Hello, " + name);
    }
}

实现多个接口

类只能 extends 一个父类,但可以 implements 任意多个接口。这是 Java 实现"多继承"能力的方式(继承行为合约,不继承实现)。

// MultiInterface.java
public class MultiInterface {
    public static void main(String[] args) {
        Robot r = new Robot();
        r.run();
        r.compute();
    }
}

interface Runnable2 { void run(); }
interface Computer  { void compute(); }

class Robot implements Runnable2, Computer {
    @Override public void run()     { System.out.println("running"); }
    @Override public void compute() { System.out.println("computing"); }
}

default 方法(Java 8+)

default 方法在接口里提供"默认实现",实现类可以不覆盖直接用。这让接口能加新方法而不破坏老的实现类——Java 8 的 Collection 接口正是借此加了 stream() 等方法。

// DefaultMethod.java
public class DefaultMethod {
    public static void main(String[] args) {
        Logger log = new ConsoleLogger();
        log.info("hello");
        log.error("boom");
    }
}

interface Logger {
    void log(String level, String msg);

    default void info(String msg)  { log("INFO",  msg); }
    default void error(String msg) { log("ERROR", msg); }
}

class ConsoleLogger implements Logger {
    @Override
    public void log(String level, String msg) {
        System.out.println("[" + level + "] " + msg);
    }
}

static 方法与常量

接口也能定义 static 方法(属于接口本身,通过 接口名.方法 调用)和 static final 常量。Comparator.comparing 等就是接口的 static 工厂方法。

// InterfaceStatic.java
public class InterfaceStatic {
    public static void main(String[] args) {
        System.out.println(MathOp.PI);
        System.out.println(MathOp.square(5));
    }
}

interface MathOp {
    double PI = 3.14159;          // 隐含 public static final

    static int square(int n) {     // static 方法
        return n * n;
    }
}

抽象类 vs 接口怎么选

  • 抽象类:你已经有了部分实现 / 字段 / 构造逻辑,让子类只补一小部分
  • 接口:你只想约定"能力",让多个无关的类都能宣称自己支持这个能力
  • 需要被多个无关类型继承 → 必然是接口(因为类不能多继承)
  • Java 8 default 方法之后,接口已经能像抽象类那样"带默认行为",新代码优先用接口

函数式接口与 @FunctionalInterface

只有一个抽象方法的接口叫"函数式接口",可以用 lambda 表达式 / 方法引用直接传值实现。加 @FunctionalInterface 注解让编译器检查:万一不小心加了第二个抽象方法会立即报错。

// FunctionalDemo.java
public class FunctionalDemo {
    public static void main(String[] args) {
        Calculator add = (a, b) -> a + b;     // lambda
        Calculator mul = (a, b) -> a * b;
        System.out.println(add.apply(2, 3));  // 5
        System.out.println(mul.apply(2, 3));  // 6
    }
}

@FunctionalInterface
interface Calculator {
    int apply(int a, int b);
    // int apply2(int a);  // 加这行就编译报错:不是函数式接口
}

标准函数式接口家族(java.util.function)

标准库内置了一组通用函数式接口,覆盖绝大多数需求,不必每次都自己定义。Stream API、Optional 等都是基于这些接口。

Function<T, R>       T -> R       (映射,最常用)
BiFunction<T, U, R>  (T, U) -> R  (二元映射)
Predicate<T>         T -> boolean (判断)
Consumer<T>          T -> void    (消费)
Supplier<T>          () -> T      (供给)
UnaryOperator<T>     T -> T       (同类型映射,Function 的特化)
BinaryOperator<T>    (T, T) -> T  (归约用)
// StdFunctional.java
import java.util.function.*;

public class StdFunctional {
    public static void main(String[] args) {
        Function<Integer, String> toHex = i -> Integer.toHexString(i);
        System.out.println(toHex.apply(255));         // ff

        Predicate<String> nonEmpty = s -> s != null && !s.isBlank();
        System.out.println(nonEmpty.test(""));        // false
        System.out.println(nonEmpty.test("hi"));      // true

        Consumer<String> print = System.out::println;
        print.accept("hello");

        Supplier<Long> now = System::currentTimeMillis;
        System.out.println(now.get());

        BinaryOperator<Integer> sum = Integer::sum;
        System.out.println(sum.apply(2, 3));          // 5
    }
}