Java 内置的函数式接口有哪些?Function、Consumer、Supplier、Predicate 怎么区分?
简化版
**Java 8 在 java.util.function 包里内置了一批「函数式接口」,它们是 Lambda 表达式的「类型」——写一个 Lambda,本质是在实现某个函数式接口。**四大核心接口按「有没有入参、有没有返回值」区分:① Function<T,R>——有入参 T、有返回值 R(「转换」:输入一个、输出另一个),核心方法 R apply(T);② Consumer<T>——有入参 T、无返回值(「消费」:拿到就用掉,如打印),void accept(T);③ Supplier<T>——无入参、有返回值 T(「供给/生产」:凭空产出一个,如工厂、延迟计算),T get();④ Predicate<T>——有入参 T、返回 boolean(「判断」:满不满足条件,如过滤),boolean test(T)。还有变体:BiFunction/BiConsumer(两个入参)、UnaryOperator(输入输出同类型的 Function)、BinaryOperator(两个同类型入参、同类型返回,如求和)、以及避免装箱的 IntFunction/ToIntFunction 等。核心记忆:看「入参 + 返回值」——有入有出是 Function、有入无出是 Consumer、无入有出是 Supplier、有入出 boolean 是 Predicate。
详细版
四大核心函数式接口:
| 接口 | 抽象方法 | 入参 | 返回 | 语义 | 典型用途 |
|---|---|---|---|---|---|
| Function<T,R> | R apply(T) | T | R | 转换 | map |
| Consumer | void accept(T) | T | 无 | 消费 | forEach |
| Supplier | T get() | 无 | T | 供给 | 延迟/工厂 |
| Predicate | boolean test(T) | T | boolean | 判断 | filter |
常见变体:
| 接口 | 说明 |
|---|---|
| BiFunction<T,U,R> | 两个入参的 Function |
| BiConsumer<T,U> | 两个入参的 Consumer |
| UnaryOperator | Function<T,T>(入出同类型) |
| BinaryOperator | BiFunction<T,T,T>(两个同类型入参→同类型,如求和) |
| IntFunction/ToIntFunction/IntPredicate | 避免装箱的基本类型特化 |
// Function:转换(T → R)
Function<String, Integer> length = s -> s.length();
Integer len = length.apply("hello"); // 5
// Consumer:消费(T → void)
Consumer<String> print = s -> System.out.println(s);
print.accept("hi");
// Supplier:供给(() → T)
Supplier<List<String>> factory = ArrayList::new;
List<String> list = factory.get();
// Predicate:判断(T → boolean)
Predicate<Integer> isPositive = n -> n > 0;
boolean b = isPositive.test(5); // true
// 组合(默认方法)
Predicate<Integer> isEven = n -> n % 2 == 0;
Predicate<Integer> both = isPositive.and(isEven); // 且
Function<String, Integer> f = length.andThen(n -> n * 2); // 先 length 再 *2
⚠️ 理解这些接口的关键,不是背名字,而是看「入参和返回值的组合」——每个 Lambda 都对应一个函数式接口,你看它「几个输入、有没有输出、输出是不是 boolean」,就知道该用哪个:有输入有输出 → Function;有输入没输出 → Consumer;没输入有输出 → Supplier;有输入输出 boolean → Predicate。这也是为什么 Stream 的方法参数正好对应它们:
map(Function)(转换)、forEach(Consumer)(消费)、filter(Predicate)(判断)、Optional.orElseGet(Supplier)(延迟供给)。另外这些接口都有默认方法做组合(Predicate.and/or/negate、Function.andThen/compose、Consumer.andThen),能把小函数拼成大函数,这是函数式编程「组合」思想的体现。
完整版教学
一、函数式接口是 Lambda 的类型
先理解「Lambda 和函数式接口的关系」:
函数式接口:只有一个抽象方法的接口(Single Abstract Method, SAM)
可以标 @FunctionalInterface 注解(编译器检查只有一个抽象方法)
Lambda 表达式的本质:实现某个函数式接口
Runnable r = () -> System.out.println("run");
→ 这个 Lambda 实现了 Runnable 的 run() 方法
→ Lambda 的"类型"就是 Runnable(函数式接口)
所以:
写一个 Lambda,编译器根据"上下文期望的函数式接口"确定它的类型
Lambda 的参数/返回值 要匹配那个接口的抽象方法
java.util.function 包:
Java 8 内置了一批通用的函数式接口
→ 不用为每种"函数形状"自己定义接口
→ Function/Consumer/Supplier/Predicate 等覆盖了常见的函数形状
为什么需要这些内置接口:
Lambda 需要一个"类型"(接口)来承载
与其到处自定义接口,不如用内置的通用接口
函数式接口是「只有一个抽象方法的接口」(SAM),可标 @FunctionalInterface。Lambda 的本质是实现某个函数式接口(Runnable r = () -> ... 实现了 run(),Lambda 的类型就是 Runnable)。java.util.function 包内置了一批通用函数式接口(Function/Consumer/Supplier/Predicate 等),不用为每种「函数形状」自己定义接口。理解「函数式接口=只有一个抽象方法的接口(SAM)、Lambda 的本质是实现函数式接口、java.util.function 内置通用接口不用自己定义」,就理解了这些接口存在的意义。
二、Function:转换(有入有出)
Function<T,R> 是「转换」——输入一个、输出另一个:
Function<T, R>:
R apply(T t) 接收 T,返回 R
语义:把 T 转换成 R
例:
Function<String, Integer> length = s -> s.length(); // String → Integer
Function<Integer, Integer> square = n -> n * n; // Integer → Integer
用在哪:
Stream.map(Function):把流的每个元素转换成另一个
→ map(s -> s.toUpperCase()) 转大写
默认方法(组合):
andThen:先做本函数,再做后一个
length.andThen(n -> n * 2) // 先算长度,再乘 2
compose:先做参数函数,再做本函数(顺序相反)
length.compose(f) // 先做 f,再做 length
特化变体:
UnaryOperator<T>:Function<T,T>(输入输出同类型,如 n -> n+1)
BiFunction<T,U,R>:两个入参(如 (a,b) -> a+b)
ToIntFunction<T>:返回 int(避免装箱)
IntFunction<R>:入参是 int
Function<T,R> 是「转换」(有入有出,R apply(T))——把 T 转成 R(s -> s.length())。用在 Stream.map(Function)(转换每个元素)。组合默认方法:andThen(先本函数再后一个)、compose(先参数函数再本函数)。变体:UnaryOperator<T>(入出同类型)、BiFunction<T,U,R>(两入参)、ToIntFunction(返回 int 避免装箱)。理解「Function 转换(有入有出,R apply(T))、用于 map、andThen/compose 组合、变体 UnaryOperator(入出同类型)/BiFunction(两入参)」,就掌握了 Function。
三、Consumer:消费(有入无出)
Consumer<T> 是「消费」——拿到就用掉,没有返回:
Consumer<T>:
void accept(T t) 接收 T,无返回
语义:消费 T(用它做点事,但不返回结果)
例:
Consumer<String> print = s -> System.out.println(s);
Consumer<User> save = user -> repository.save(user);
用在哪:
Stream.forEach(Consumer):对每个元素做操作(打印、保存)
Optional.ifPresent(Consumer):有值时消费它
list.forEach(System.out::println)
默认方法:
andThen:连续消费(先做本 Consumer,再做后一个)
print.andThen(log).accept(x) // 先打印,再记日志
特化变体:
BiConsumer<T,U>:两个入参(如 Map.forEach((k,v) -> ...))
IntConsumer:消费 int(避免装箱)
特点:Consumer 有副作用(它就是用来"做事"的,如打印、保存)
→ 和 Function/Predicate 的"纯函数"不同,Consumer 是"执行动作"
Consumer<T> 是「消费」(有入无出,void accept(T))——拿到 T 做点事但不返回(s -> print(s))。用在 Stream.forEach(Consumer)、Optional.ifPresent。组合:andThen(连续消费)。变体:BiConsumer<T,U>(两入参,Map.forEach)。特点:Consumer 有副作用(就是用来「做事」的,和 Function/Predicate 的纯函数不同)。理解「Consumer 消费(有入无出,void accept(T))、用于 forEach/ifPresent、有副作用(执行动作)、变体 BiConsumer(Map.forEach)」,就掌握了 Consumer。
四、Supplier:供给(无入有出)
Supplier<T> 是「供给」——凭空产出一个:
Supplier<T>:
T get() 无入参,返回 T
语义:供给/生产一个 T(凭空产出)
例:
Supplier<List<String>> factory = ArrayList::new; // 生产一个 List
Supplier<Long> now = System::currentTimeMillis; // 生产当前时间
Supplier<String> random = () -> UUID.randomUUID().toString();
用在哪(核心价值:延迟/惰性):
Optional.orElseGet(Supplier):值不存在时才调 get 生产默认值
→ 相比 orElse(value),orElseGet 是"惰性"的(用到才生产)
Logger.debug(() -> 复杂计算):只有 debug 开启才计算(延迟求值)
Stream.generate(Supplier):无限流生成
为什么 Supplier 重要——延迟求值:
普通传值:orElse(expensiveDefault()) ← 不管用不用都先算好
用 Supplier:orElseGet(() -> expensiveDefault()) ← 用到才算
→ 避免不必要的计算(惰性、延迟)
特化变体:IntSupplier/BooleanSupplier 等
Supplier<T> 是「供给」(无入有出,T get())——凭空产出一个 T(ArrayList::new 生产 List)。核心价值是延迟/惰性:Optional.orElseGet(Supplier)(值不存在才生产默认值,相比 orElse(value) 是惰性的)、Logger.debug(() -> 复杂计算)(只有 debug 开启才计算)、Stream.generate。延迟求值避免不必要的计算(orElse 不管用不用都先算、orElseGet 用到才算)。理解「Supplier 供给(无入有出,T get())、核心价值延迟/惰性(orElseGet 用到才生产、Logger 延迟计算)、避免不必要的计算」,就掌握了 Supplier。
五、Predicate:判断(有入出 boolean)
Predicate<T> 是「判断」——满不满足条件:
Predicate<T>:
boolean test(T t) 接收 T,返回 boolean
语义:判断 T 是否满足某条件
例:
Predicate<Integer> isPositive = n -> n > 0;
Predicate<String> isEmpty = String::isEmpty;
用在哪:
Stream.filter(Predicate):过滤(保留满足条件的)
Stream.anyMatch/allMatch/noneMatch(Predicate):匹配判断
removeIf(Predicate):移除满足条件的
默认方法(逻辑组合,很实用):
and:两个条件都满足 isPositive.and(isEven)
or:满足其一 isPositive.or(isZero)
negate:取反 isPositive.negate()
→ 把多个简单判断组合成复杂判断
静态方法:
Predicate.isEqual(x):判断是否等于 x
Predicate.not(pred):取反(Java 11+)
特化变体:IntPredicate/LongPredicate(避免装箱)
BiPredicate<T,U>:两个入参的判断
Predicate<T> 是「判断」(有入出 boolean,boolean test(T))——判断是否满足条件(n -> n > 0)。用在 Stream.filter(Predicate)(过滤)、anyMatch/allMatch、removeIf。逻辑组合默认方法很实用:and(都满足)、or(满足其一)、negate(取反),把简单判断组合成复杂判断。变体:BiPredicate<T,U>(两入参)。理解「Predicate 判断(有入出 boolean,boolean test(T))、用于 filter/anyMatch/removeIf、and/or/negate 逻辑组合、变体 BiPredicate」,就掌握了 Predicate。
六、区分方法与组合思想
总结「怎么区分」和「组合思想」:
区分方法(看入参和返回值):
有返回值 无返回值
有入参 Function<T,R> Consumer<T>
(返回boolean → Predicate<T>)
无入参 Supplier<T> Runnable(无入无出)
→ 有入有出=Function、有入无出=Consumer、
无入有出=Supplier、有入出boolean=Predicate、无入无出=Runnable
对应 Stream 方法(记忆锚点):
map(Function) 转换
filter(Predicate) 过滤
forEach(Consumer) 消费
orElseGet(Supplier) 惰性供给
组合思想(默认方法):
Function.andThen/compose:函数串联
Predicate.and/or/negate:条件逻辑组合
Consumer.andThen:消费串联
→ 把小的、单一职责的函数,组合成复杂的函数
→ 这是函数式编程"组合优于继承"的体现
常见变体总结:
Bi 前缀:两个入参(BiFunction/BiConsumer/BiPredicate)
UnaryOperator/BinaryOperator:入出同类型的 Function/BiFunction
Int/Long/Double 前缀:基本类型特化(避免装箱,提性能)
区分方法看「入参 + 返回值」:有入有出=Function、有入无出=Consumer、无入有出=Supplier、有入出 boolean=Predicate、无入无出=Runnable。对应 Stream 方法(记忆锚点):map(Function)/filter(Predicate)/forEach(Consumer)/orElseGet(Supplier)。组合思想:默认方法(Function.andThen/Predicate.and-or-negate/Consumer.andThen)把小函数组合成复杂函数。变体:Bi 前缀(两入参)、UnaryOperator/BinaryOperator(入出同类型)、Int/Long/Double 前缀(基本类型特化避免装箱)。理解「区分看入参+返回值(有入有出 Function/有入无出 Consumer/无入有出 Supplier/有入出 boolean Predicate)、对应 Stream 方法、组合默认方法、变体 Bi/UnaryOperator/基本类型特化」,就掌握了区分和组合的全貌。
记忆钩子:「Java 内置函数式接口(java.util.function),Lambda 的类型;四大核心看入参+返回值:①Function<T,R>(有入有出,R apply(T),转换,用于 map,andThen/compose 组合)②Consumer
(有入无出,void accept(T),消费/有副作用,用于 forEach)③Supplier 。(无入有出,T get(),供给/延迟惰性,用于 orElseGet 用到才生产)④Predicate (有入出 boolean,boolean test(T),判断,用于 filter,and/or/negate 组合);区分:有入有出=Function、有入无出=Consumer、无入有出=Supplier、有入出 boolean=Predicate;变体:Bi 前缀(两入参)、UnaryOperator/BinaryOperator(入出同类型)、Int/Long/Double 前缀(避免装箱)」
七、常见误区与追问
- 误区:这些函数式接口要死记名字。 关键是看「入参和返回值的组合」:有输入有输出→Function、有输入没输出→Consumer、没输入有输出→Supplier、有输入输出 boolean→Predicate、无输入无输出→Runnable;理解了组合就不用背名字。
- 误区:Consumer 和 Function 差不多。 Function 有返回值(转换 T→R,纯函数);Consumer 无返回值(消费 T,有副作用、执行动作如打印/保存);一个是「转换出新值」、一个是「拿值做事不返回」。
- 误区:orElse 和 orElseGet 一样。 orElse(value) 不管用不用都会先计算 value;orElseGet(Supplier) 是惰性的,只有 Optional 为空时才调 Supplier 生产默认值;如果默认值计算昂贵,orElseGet 避免了不必要的计算——这是 Supplier 延迟求值的价值。
- 误区:Predicate 只能表达单一条件。 Predicate 有 and/or/negate 默认方法——可以把多个简单 Predicate 组合成复杂条件:isPositive.and(isEven)(都满足)、a.or(b)(满足其一)、p.negate()(取反);这是函数式「组合」思想的体现。
- 追问:为什么有 IntFunction、ToIntFunction 这些基本类型特化接口? 为了避免自动装箱/拆箱的性能开销——Function<Integer, Integer> 会把 int 装箱成 Integer(有开销),而 IntUnaryOperator、ToIntFunction
、IntPredicate 等直接用基本类型 int,避免装箱,在大量数据处理(如 IntStream)时性能更好。 - 追问:Function 的 andThen 和 compose 有什么区别? andThen 是「先做本函数、再做参数函数」:f.andThen(g) 等于 x -> g(f(x));compose 是「先做参数函数、再做本函数」:f.compose(g) 等于 x -> f(g(x));顺序相反,andThen 是正向串联、compose 是反向。
- 追问:UnaryOperator 和 Function 是什么关系? UnaryOperator
继承自 Function<T,T>——它是「输入和输出同类型」的 Function 的特例(如 n -> n+1 是 UnaryOperator );同理 BinaryOperator 是 BiFunction<T,T,T> 的特例(两个同类型入参、同类型返回,如 Integer::sum);用它们语义更明确(表示「同类型的运算」)。
八、加强记忆
Java 8 在 java.util.function 内置了一批「函数式接口」——它们是 Lambda 的「类型」(函数式接口 = 只有一个抽象方法 SAM,Lambda 本质是实现它)。四大核心,按「入参 + 返回值」区分:① Function<T,R>(有入有出,R apply(T),转换,用于 map,andThen/compose 组合);② Consumer<T>(有入无出,void accept(T),消费/有副作用,用于 forEach);③ Supplier<T>(无入有出,T get(),供给/延迟惰性,用于 orElseGet——值不存在才生产、避免不必要计算);④ Predicate<T>(有入出 boolean,boolean test(T),判断,用于 filter,and/or/negate 逻辑组合)。区分口诀:有入有出=Function、有入无出=Consumer、无入有出=Supplier、有入出 boolean=Predicate、无入无出=Runnable。对应 Stream 方法:map(Function)/forEach(Consumer)/orElseGet(Supplier)/filter(Predicate)。变体:Bi 前缀(两入参,BiFunction/BiConsumer/BiPredicate)、UnaryOperator<T>(Function<T,T> 入出同类型)、BinaryOperator<T>(两同类型入参→同类型,如求和)、Int/Long/Double 前缀(基本类型特化避免装箱)。这些接口都有默认方法做组合,体现函数式「组合」思想。一句话「内置函数式接口是 Lambda 的类型,看入参+返回值区分:Function(有入有出/转换/map)、Consumer(有入无出/消费/forEach)、Supplier(无入有出/延迟供给/orElseGet)、Predicate(有入出 boolean/判断/filter);变体 Bi(两入参)/UnaryOperator(入出同类型)/基本类型特化(避免装箱);默认方法组合小函数」。