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 等 |
自定义特化接口
当需要处理 short
到 byte
转换时,可自定义接口:
@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 支持
掌握这些接口能显著提升代码简洁性和表达力,是函数式编程的基石。