1. 引言

本文系统梳理了 Java 8 中核心函数式接口的用法、典型场景及在标准库中的应用,为日常开发提供实用参考。

2. Java 8 中的 Lambda 表达式

Java 8 引入的 Lambda 表达式是语法层面的重大革新。Lambda 本质是匿名函数,可作为一等公民使用(如作为参数传递或方法返回值)。

在 Java 8 之前,封装单一功能通常需要创建完整类,导致大量样板代码。Lambda 彻底改变了这种模式,用更简洁的方式表达函数逻辑。

⚠️ 关于 Lambda 最佳实践,可参考《Lambda 表达式与函数式接口:技巧与最佳实践》一文。本文聚焦 java.util.function 包中的核心接口。

3. 函数式接口

所有函数式接口都应添加 @FunctionalInterface 注解。这有两个作用:

  • 明确声明接口用途
  • 让编译器检查接口是否符合函数式接口规范

核心规则:任何包含 SAM(Single Abstract Method)的接口都是函数式接口,其实现可被 Lambda 表达式替代。

✅ 注意:Java 8 的 default 方法不计入抽象方法,因此函数式接口可包含多个默认方法(参考 Function 接口文档)。

4. Function 接口

最通用的函数式接口是 Function<T, R>,表示接收一个参数并返回结果的函数:

public interface Function<T, R> { ... }

典型应用场景

Map.computeIfAbsent 方法展示了 Function 的经典用法:当键不存在时,通过函数计算新值:

Map<String, Integer> nameMap = new HashMap<>();
Integer value = nameMap.computeIfAbsent("John", s -> s.length());

💡 技巧:当 Lambda 表达式仅调用现有方法时,可直接替换为方法引用:

Integer value = nameMap.computeIfAbsent("John", String::length);

函数组合

Function 提供了 compose 方法实现函数链式调用:

Function<Integer, String> intToString = Object::toString;
Function<String, String> quote = s -> "'" + s + "'";

Function<Integer, String> quoteIntToString = quote.compose(intToString);

assertEquals("'5'", quoteIntToString.apply(5));

quoteIntToString 先执行 intToString 再执行 quote

5. 原生类型函数特化

由于泛型不支持原生类型,Java 提供了针对 double/int/long 的特化接口:

输入类型 返回类型 接口示例
原生类型 泛型 IntFunction<R>, LongFunction<R>, DoubleFunction<R>
泛型 原生类型 ToIntFunction<T>, ToLongFunction<T>, ToDoubleFunction<T>
原生类型 原生类型 DoubleToIntFunction, IntToLongFunction

自定义特化接口

当需要处理 shortbyte 转换时,可自定义接口:

@FunctionalInterface
public interface ShortToByteFunction {
    byte applyAsByte(short s);
}

使用示例:

public byte[] transformArray(short[] array, ShortToByteFunction function) {
    byte[] transformedArray = new byte[array.length];
    for (int i = 0; i < array.length; i++) {
        transformedArray[i] = function.applyAsByte(array[i]);
    }
    return transformedArray;
}

// 调用示例
short[] array = {(short) 1, (short) 2, (short) 3};
byte[] transformedArray = transformArray(array, s -> (byte) (s * 2));

byte[] expectedArray = {(byte) 2, (byte) 4, (byte) 6};
assertArrayEquals(expectedArray, transformedArray);

6. 双参数函数特化

处理双参数 Lambda 需使用带 "Bi" 前缀的接口:

  • BiFunction<T, U, R>:泛型参数和返回值
  • ToDoubleBiFunction<T, U> 等:返回原生类型

典型应用:Map 批量更新

Map.replaceAll 使用 BiFunction 计算新值:

Map<String, Integer> salaries = new HashMap<>();
salaries.put("John", 40000);
salaries.put("Freddy", 30000);
salaries.put("Samuel", 50000);

// 非 Freddy 员工加薪 10000
salaries.replaceAll((name, oldValue) -> 
  name.equals("Freddy") ? oldValue : oldValue + 10000);

7. Supplier 接口

Supplier 表示无参函数,常用于延迟计算或生成序列。

延迟计算示例

public double squareLazy(Supplier<Double> lazyValue) {
    return Math.pow(lazyValue.get(), 2);
}

// 模拟耗时计算
Supplier<Double> lazyValue = () -> {
    Uninterruptibles.sleepUninterruptibly(1000, TimeUnit.MILLISECONDS);
    return 9d;
};

Double valueSquared = squareLazy(lazyValue);

序列生成:斐波那契数列

int[] fibs = {0, 1};
Stream<Integer> fibonacci = Stream.generate(() -> {
    int result = fibs[1];
    int fib3 = fibs[0] + fibs[1];
    fibs[0] = fibs[1];
    fibs[1] = fib3;
    return result;
});

⚠️ 注意:Lambda 中使用的外部变量必须是 effectively final,因此用数组而非变量保存状态。

原生类型特化

BooleanSupplier/DoubleSupplier/LongSupplier/IntSupplier 对应返回原生类型的场景。

8. Consumer 接口

Consumer 表示接收参数但不返回值的操作(副作用操作)。

典型应用:遍历处理

List<String> names = Arrays.asList("John", "Freddy", "Samuel");
names.forEach(name -> System.out.println("Hello, " + name));

双参数版本:BiConsumer

遍历 Map 条目时特别有用:

Map<String, Integer> ages = new HashMap<>();
ages.put("John", 25);
ages.put("Freddy", 24);
ages.put("Samuel", 30);

ages.forEach((name, age) -> 
    System.out.println(name + " is " + age + " years old"));

原生类型特化

  • DoubleConsumer/IntConsumer/LongConsumer:单原生参数
  • ObjDoubleConsumer/ObjIntConsumer/ObjLongConsumer:泛型+原生参数

9. Predicate 接口

Predicate 表示返回布尔值的函数,常用于条件判断。

集合过滤示例

List<String> names = Arrays.asList("Angela", "Aaron", "Bob", "Claire", "David");

List<String> namesWithA = names.stream()
  .filter(name -> name.startsWith("A"))
  .collect(Collectors.toList());

原生类型特化

IntPredicate/DoublePredicate/LongPredicate 处理原生类型参数。

10. Operator 接口

Operator 是特殊函数:输入和输出类型相同。

UnaryOperator:单参数操作

List.replaceAll 要求转换前后类型一致:

List<String> names = Arrays.asList("bob", "josh", "megan");
names.replaceAll(String::toUpperCase); // 方法引用简化

BinaryOperator:归约操作

Stream.reduce 的经典应用:

List<Integer> values = Arrays.asList(3, 5, 8, 9, 12);

int sum = values.stream()
  .reduce(0, (i1, i2) -> i1 + i2);

✅ 关键要求:BinaryOperator 必须满足结合律(op(a, op(b,c)) == op(op(a,b), c)),这是并行归约的基础。

原生类型特化

  • 单参数:DoubleUnaryOperator/IntUnaryOperator/LongUnaryOperator
  • 双参数:DoubleBinaryOperator/IntBinaryOperator/LongBinaryOperator

11. 遗留函数式接口

Java 8 之前的接口只要符合 SAM 规范,也可用作 Lambda。典型代表:

  • Runnable:并发任务
  • Callable:带返回值的并发任务

简化并发代码

Thread thread = new Thread(() -> System.out.println("Hello From Another Thread"));
thread.start();

12. 总结

本文系统梳理了 Java 8 核心函数式接口:

  • 基础接口Function/Supplier/Consumer/Predicate
  • 特化接口:针对原生类型和双参数场景的优化版本
  • 操作接口UnaryOperator/BinaryOperator 及其特化
  • 遗留兼容Runnable/Callable 等历史接口的 Lambda 支持

掌握这些接口能显著提升代码简洁性和表达力,是函数式编程的基石。


原始标题:Functional Interfaces in Java | Baeldung