源本科技 | 码上会

Java 函数式接口

2026/01/22
12
0

学习目标

  • 理解 函数式接口(Functional Interface) 的定义与核心特征

  • 掌握 @FunctionalInterface 注解的作用与使用规范

  • 熟悉 Java 8 引入的四大核心函数式接口:ConsumerPredicateFunctionSupplier

  • 能够结合 Lambda 表达式编写简洁、高效的代码

  • 了解函数式接口在 Stream API 和集合操作中的典型应用场景


什么是函数式接口

函数式接口是仅包含一个抽象方法的接口。这一特性使其能够与 Lambda 表达式方法引用 无缝配合,从而简化代码结构。

关键点:只要接口中有且仅有一个抽象方法,它就是函数式接口——无论是否显式使用 @FunctionalInterface 注解。

使用 Lambda 实现 Runnable

public class MainApp {
    public static void main(String[] args) {
        // 使用 Lambda 表达式实现 Runnable 接口
        new Thread(() -> System.out.println("新线程已启动")).start();
    }
}

输出

新线程已启动

说明Runnable 接口只有一个抽象方法 run(),因此是函数式接口。Lambda 表达式 () -> ... 实际上就是对 run() 方法的实现。


@FunctionalInterface

虽然该注解不是必需的,但强烈建议使用,因为它能帮助编译器在早期发现错误。

自定义函数式接口

@FunctionalInterface
interface Square {
    int calculate(int x);
}

class MainApp {
    public static void main(String[] args) {
        int a = 5;
        // 使用 Lambda 实现 calculate 方法
        Square s = (int x) -> x * x;
        int result = s.calculate(a);
        System.out.println(result); // 输出:25
    }
}

如果在标注了 @FunctionalInterface 的接口中添加第二个抽象方法,编译器将报错:
“Unexpected @FunctionalInterface annotation”。


Java 8 之前的写法

在 Java 8 之前,必须使用匿名内部类来实现接口:

new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("新线程已启动");
    }
}).start();

相比之下,Lambda 表达式显著减少了样板代码,提升了可读性。


Java 核心函数式接口

Java 8 在 java.util.function 包中提供了大量预定义的函数式接口,广泛用于 Stream、集合和函数式编程。

消费型接口

Consumer<T>

  • 作用:接收一个参数,无返回值(常用于打印、日志、副作用操作)

  • 核心方法void accept(T t)

  • 变体IntConsumerDoubleConsumerLongConsumer

Consumer<String> printer = value -> System.out.println(value);
printer.accept("Hello");

断言型接口

Predicate<T>

  • 作用:接收一个参数,返回 boolean(常用于过滤条件)

  • 核心方法boolean test(T t)

  • 变体IntPredicateDoublePredicateLongPredicate

示例:过滤以 "G" 开头的字符串

import java.util.*;
import java.util.function.Predicate;

class MainApp {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Good", "God", "g1", "QA", "Goods");
        
        Predicate<String> startsWithG = s -> s.startsWith("G");
        
        for (String name : names) {
            if (startsWithG.test(name)) {
                System.out.println(name);
            }
        }
    }
}

转换型接口

Function<T, R>

  • 作用:接收一个参数,返回一个结果(常用于数据映射或转换)

  • 核心方法R apply(T t)

  • 常见变体

    • UnaryOperator<T>:输入输出类型相同(如 T -> T

    • BiFunction<T, U, R>:接收两个参数,返回一个结果

    • BinaryOperator<T>BiFunction 的特例,输入输出类型相同

Function<Integer, Integer> square = x -> x * x;
System.out.println(square.apply(4)); // 输出:16

提供型接口

Supplier<T>

  • 作用不接收参数,但返回一个结果(常用于工厂方法、延迟初始化)

  • 核心方法T get()

  • 变体BooleanSupplierIntSupplierDoubleSupplierLongSupplier

Supplier<String> message = () -> "你好,世界!";
System.out.println(message.get()); // 输出:你好,世界!

常用函数式接口

接口名称

描述

核心方法

Runnable

表示可在线程中执行的任务

void run()

Comparable<T>

用于对象排序比较

int compareTo(T o)

Callable<V>

可返回结果或抛出异常的任务

V call() throws Exception

Consumer<T>

消费一个参数,无返回

void accept(T t)

Predicate<T>

判断条件,返回布尔值

boolean test(T t)

Function<T, R>

转换输入为输出

R apply(T t)

Supplier<T>

无输入,提供输出

T get()

BiConsumer<T, U>

消费两个参数

void accept(T t, U u)

BiPredicate<T, U>

对两个参数做布尔判断

boolean test(T t, U u)

BiFunction<T, U, R>

用两个参数生成结果

R apply(T t, U u)

UnaryOperator<T>

同类型输入输出的 Function

T apply(T t)

BinaryOperator<T>

同类型双输入单输出的 BiFunction

T apply(T t1, T t2)


重点总结

  • 函数式接口 = 仅含一个抽象方法 的接口。

  • @FunctionalInterface可选但推荐的编译期检查工具。

  • Lambda 表达式让函数式接口的实现变得极其简洁。

  • ConsumerPredicateFunctionSupplier 是最常用的四大函数式接口。

  • 这些接口是 Java Stream API 的基石,广泛应用于集合的过滤、映射、遍历等操作。


思考题

  1. 为什么 Comparable 被视为函数式接口?它是否可以与 Lambda 表达式一起使用?请举例说明。

  2. 假设你需要对一个整数列表进行如下操作:只保留偶数,然后每个数平方,最后求和。请使用 PredicateFunctionStream 完成该任务。

  3. 如果一个接口继承了两个函数式接口,且这两个父接口的抽象方法签名不同,那么子接口还是函数式接口吗?为什么?