https://www.cnblogs.com/dgwblog/p/11739500.html
https://juejin.cn/post/6844903892166148110
1. Supplier<T>
- 数据的供给者 🎁
接口定义:@FunctionalInterface public interface Supplier<T> { T get(); }
核心作用:
Supplier
接口的核心职责是生产或提供数据,它不接受任何参数,但会返回一个 T
类型的结果。你可以把它想象成一个“工厂”或者“源头”,当你需要一个特定类型的对象时,就调用它的 get()
方法。
方法详解:
T get()
: 这是Supplier
接口中唯一的抽象方法。调用它时,会执行你提供的 Lambda 表达式或方法引用所定义的逻辑,并返回一个结果。
常见应用场景:
- 延迟加载/创建对象:当某个对象的创建成本较高,或者并非立即需要时,可以使用
Supplier
来推迟其创建,直到真正使用时才调用get()
。 - 生成默认值或配置信息:提供一个默认对象或从某个源(如配置文件、数据库)获取配置。
- 生成随机数据:如示例中的随机数生成器。
- 作为工厂方法:在更复杂的场景中,
Supplier
可以作为创建对象的简单工厂。
您的示例代码分析 (SupplierExample.java
):
import java.util.Random;
import java.util.function.Supplier;
public class SupplierExample {
// 示例方法1: 接收一个 Supplier 来获取随机整数
public static Integer getRandomNumber(Supplier<Integer> randomNumberSupplier) {
// 调用 randomNumberSupplier 的 get 方法来执行其提供的逻辑
return randomNumberSupplier.get();
}
// 示例方法2: 接收一个 Supplier 来创建问候语字符串
public static String createGreetingMessage(Supplier<String> greetingSupplier) {
return greetingSupplier.get();
}
public static void main(String[] args) {
// 场景1: 获取随机数
// Lambda 表达式实现 Supplier: () -> new Random().nextInt(100)
// 这个 Lambda 不接受参数,返回一个 0-99 的随机整数
Supplier<Integer> randomIntSupplier = () -> new Random().nextInt(100);
Integer num = getRandomNumber(randomIntSupplier); // 传递行为
System.out.println("随机数: " + num);
// 场景2: 获取固定数字
// Lambda 表达式实现 Supplier: () -> 42
// 这个 Lambda 总是返回固定的数字 42
Supplier<Integer> fixedIntSupplier = () -> 42;
Integer fixedNum = getRandomNumber(fixedIntSupplier);
System.out.println("固定数字: " + fixedNum);
// 场景3: 创建不同的问候语
Supplier<String> englishGreeting = () -> "Hello, World!";
System.out.println(createGreetingMessage(englishGreeting)); // 输出: Hello, World!
Supplier<String> spanishGreeting = () -> "¡Hola, Mundo!";
System.out.println(createGreetingMessage(spanishGreeting)); // 输出: ¡Hola, Mundo!
}
}
代码解读:
getRandomNumber
和createGreetingMessage
方法本身并不关心数字或字符串是如何产生的,它们只依赖传入的Supplier
来提供结果。这体现了行为参数化——方法接受行为(通过函数式接口)作为参数。- 在
main
方法中:randomIntSupplier
: 定义了一个行为——“生成一个0到99的随机整数”。fixedIntSupplier
: 定义了另一个行为——“总是提供数字42”。englishGreeting
和spanishGreeting
: 定义了不同的行为来提供特定的字符串。
- 通过将不同的
Supplier
实现传递给同一个方法 (getRandomNumber
或createGreetingMessage
),我们可以获得不同的结果,而无需修改方法本身。
关键益处:
- 灵活性:可以轻松替换不同的供给逻辑。
- 解耦:数据的使用者和数据的生产者解耦。
- 可测试性:可以方便地传入 mock 的
Supplier
进行单元测试。
2. Function<T, R>
- 数据的转换器/映射器 🔄
接口定义:@FunctionalInterface public interface Function<T, R> { R apply(T t); }
核心作用:
Function
接口的核心职责是将一个类型 T
的输入参数转换或映射成另一个类型 R
的输出结果。它就像一个数据处理管道中的一个环节,接收数据,进行处理,然后传递给下一个环节。
方法详解:
R apply(T t)
: 这是Function
的核心方法。它接受一个T
类型的参数t
,对其执行Lambda表达式或方法引用中定义的转换逻辑,并返回一个R
类型的结果。
常见应用场景:
- 数据转换:例如,将字符串转换为整数,将日期对象格式化为字符串,或者如示例中计算字符串长度、数字平方。
- 对象属性提取:从一个复杂对象中提取某个特定属性的值。例如,
Person -> String (person.getName())
。 - 链式操作:
Function
接口提供了andThen()
和compose()
默认方法,可以方便地将多个Function
串联起来形成一个处理流水线。
您的示例代码分析 (FunctionExample.java
):
import java.util.function.Function;
public class FunctionExample {
// 示例方法1: 接收一个 Function 来计算字符串长度
public static Integer getStringLength(String text, Function<String, Integer> lengthCalculator) {
// 调用 lengthCalculator 的 apply 方法,传入 text,执行其转换逻辑
return lengthCalculator.apply(text);
}
// 示例方法2: 接收一个 Function 来计算数字的平方
public static Integer squareNumber(Integer number, Function<Integer, Integer> squareFunction) {
return squareFunction.apply(number);
}
public static void main(String[] args) {
// 场景1: 计算字符串长度
String myString = "Java Functional";
// Lambda 表达式实现 Function: s -> s.length()
// 这个 Lambda 接受一个 String s,返回其长度 (Integer)
Function<String, Integer> lengthLambda = s -> s.length();
Integer length = getStringLength(myString, lengthLambda);
System.out.println("字符串 '" + myString + "' 的长度是: " + length);
// 使用方法引用 (Method Reference) 实现 Function: String::length
// String::length 等价于 s -> s.length(),更为简洁
Integer lengthUsingMethodRef = getStringLength("Test", String::length);
System.out.println("字符串 'Test' 的长度是: " + lengthUsingMethodRef);
// 场景2: 计算数字平方
Integer num = 5;
// Lambda 表达式实现 Function: n -> n * n
// 接受一个 Integer n,返回 n 的平方 (Integer)
Function<Integer, Integer> squareLambda = n -> n * n;
Integer squared = squareNumber(num, squareLambda);
System.out.println(num + " 的平方是: " + squared);
Integer anotherNum = 10;
// 多行 Lambda 表达式
Function<Integer, Integer> verboseSquareLambda = x -> {
System.out.println("正在计算 " + x + " 的平方..."); // Lambda 可以包含多条语句
return x * x;
};
Integer squaredAgain = squareNumber(anotherNum, verboseSquareLambda);
System.out.println(anotherNum + " 的平方是: " + squaredAgain);
}
}
代码解读:
getStringLength
和squareNumber
方法定义了操作的框架,但具体的转换逻辑由传入的Function
对象决定。- 在
main
方法中:s -> s.length()
和String::length
都是Function<String, Integer>
的实例,它们定义了“从字符串到其长度整数”的转换。n -> n * n
是Function<Integer, Integer>
的实例,定义了“从整数到其平方整数”的转换。- 多行 Lambda
verboseSquareLambda
展示了更复杂的转换逻辑可以被封装。
- 这种方式使得我们可以为同一个通用方法(如
getStringLength
)提供不同的转换策略。
关键益处:
- 代码复用:通用的转换逻辑可以被封装成
Function
并在多处使用。 - 可组合性:通过
andThen
和compose
可以构建复杂的转换流。 - 清晰性:将数据转换的意图明确表达出来。
3. BiConsumer<T, U>
- 双参数的消费者/执行者 🤝
接口定义:@FunctionalInterface public interface BiConsumer<T, U> { void accept(T t, U u); }
核心作用:
BiConsumer
接口的核心职责是对两个不同类型(或相同类型)的输入参数 T
和 U
执行某个操作或产生某种副作用,但它不返回任何结果 (void)。你可以把它看作是需要两个输入才能完成其工作的“执行者”。
方法详解:
void accept(T t, U u)
: 这是BiConsumer
的核心方法。它接受两个参数t
和u
,并对它们执行 Lambda 表达式或方法引用中定义的操作。由于返回类型是void
,它通常用于执行有副作用的操作,如打印、修改集合、更新数据库等。
常见应用场景:
- 处理键值对:非常适合用于迭代
Map
的条目,如Map.forEach()
方法就接受一个BiConsumer<K, V>
。 - 同时操作两个相关对象:当一个操作需要两个输入,并且不产生新的独立结果时。例如,将一个对象的属性设置到另一个对象上。
- 配置或初始化:使用两个参数来配置某个组件。
您的示例代码分析 (BiConsumerExample.java
):
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiConsumer;
public class BiConsumerExample {
// 示例方法1: 接收 BiConsumer 来打印键和值
public static <K, V> void printMapEntry(K key, V value, BiConsumer<K, V> entryPrinter) {
// 调用 entryPrinter 的 accept 方法,传入 key 和 value
entryPrinter.accept(key, value);
}
// 示例2 在 main 中直接演示了更常见的 Map 操作方式
// 辅助内部类,如果 BiConsumer 需要一次性接收多个信息 (在此示例中未直接用于核心 BiConsumer 演示)
// static class Pair<F, S> {
// F first; S second;
// Pair(F f, S s) { this.first = f; this.second = s; }
// }
public static void main(String[] args) {
// 场景1: 使用 printMapEntry 打印键值
// Lambda 表达式实现 BiConsumer: (k, v) -> System.out.println("键: " + k + ", 值: " + v)
// 接受一个 String k 和一个 Integer v,然后打印它们
BiConsumer<String, Integer> simplePrinter = (k, v) -> System.out.println("键: " + k + ", 值: " + v);
printMapEntry("年龄", 30, simplePrinter);
printMapEntry("数量", 100, simplePrinter);
// 场景2: 使用 BiConsumer 来填充 Map
Map<String, String> config = new HashMap<>();
// Lambda 表达式实现 BiConsumer: (key, value) -> config.put(key, value)
// 这个 Lambda 捕获了外部的 'config' Map 对象。
// 它接受 String key 和 String value,并将它们放入 config Map 中。
BiConsumer<String, String> mapPutter = (key, value) -> config.put(key, value);
mapPutter.accept("user.name", "Alice"); // 执行操作:config.put("user.name", "Alice")
mapPutter.accept("user.role", "Admin"); // 执行操作:config.put("user.role", "Admin")
System.out.println("配置Map: " + config);
// 场景3: Map.forEach() 的典型用法
// Map 的 forEach 方法直接接受一个 BiConsumer<K, V>
System.out.println("遍历Map:");
config.forEach((key, value) -> { // 这里的 (key, value) -> {...} 就是一个 BiConsumer
System.out.println("配置项 - " + key + ": " + value);
});
}
}
代码解读:
printMapEntry
方法接受一个键、一个值和一个BiConsumer
,该BiConsumer
定义了如何处理这对键值。- 在
main
方法中:simplePrinter
: 定义了一个行为——“接收一个键和一个值,并将它们打印到控制台”。mapPutter
: 定义了一个行为——“接收一个键和一个字符串值,并将它们存入外部的config
Map”。这里 Lambda 表达式捕获了外部变量config
,这是一种常见的用法。config.forEach(...)
: 这是BiConsumer
最经典的用例之一。forEach
方法遍历Map
中的每个条目,并对每个键值对执行提供的BiConsumer
逻辑。
关键益处:
- 处理成对数据:专门设计用于需要两个输入的场景。
- 与集合(尤其是Map)的良好集成:
Map.forEach
是一个很好的例子。 - 封装副作用操作:可以将对两个参数的副作用操作(如修改、打印)封装起来。
4. Consumer<T>
- 数据的消费者/执行者 🍽️
接口定义:@FunctionalInterface public interface Consumer<T> { void accept(T t); }
核心作用:
Consumer
接口的核心职责是对单个输入参数 T
执行某个操作或产生某种副作用,它不返回任何结果 (void)。你可以把它看作是数据的“终点”或某个动作的执行者,它“消费”数据但不产生新的输出数据。
方法详解:
void accept(T t)
: 这是Consumer
的核心方法。它接受一个T
类型的参数t
,并对其执行 Lambda 表达式或方法引用中定义的操作。因为返回void
,它主要用于执行那些为了副作用而进行的操作(如打印、修改对象状态、写入文件等)。
常见应用场景:
- 迭代集合并处理元素:
List.forEach()
方法接受一个Consumer<T>
,对列表中的每个元素执行指定操作。 - 打印/日志记录:将信息输出到控制台、文件或其他日志系统。
- 更新对象状态:修改传入对象的属性。
- 回调:在某个异步操作完成后执行一个
Consumer
定义的动作。
您的示例代码分析 (ConsumerExample.java
):
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
public class ConsumerExample {
// 示例方法1: 接收 Consumer 来展示单个项目
public static <T> void displayItem(T item, Consumer<T> itemDisplayer) {
// 调用 itemDisplayer 的 accept 方法,传入 item,执行其消费逻辑
itemDisplayer.accept(item);
}
// 示例方法2: 接收 Consumer 来处理列表中的每个项目
public static <T> void processListItems(List<T> list, Consumer<T> itemProcessor) {
for (T item : list) {
itemProcessor.accept(item); // 对列表中的每个 item 执行 itemProcessor 的逻辑
}
}
public static void main(String[] args) {
// 场景1: 使用 displayItem 打印信息
// Lambda 表达式实现 Consumer: message -> System.out.println("消息: " + message)
// 接受一个 String message,然后打印它
Consumer<String> consolePrinter = message -> System.out.println("消息: " + message);
displayItem("你好,函数式接口!", consolePrinter);
// 多行 Lambda 实现 Consumer,进行更复杂的打印
Consumer<Integer> detailedPrinter = number -> {
System.out.println("--- 数字详情 ---");
System.out.println("值: " + number);
System.out.println("是否偶数: " + (number % 2 == 0));
System.out.println("----------------");
};
displayItem(10, detailedPrinter);
displayItem(7, System.out::println); // 方法引用: System.out::println 等价于 x -> System.out.println(x)
// 场景2: 使用 processListItems 处理列表
List<String> names = Arrays.asList("爱丽丝", "鲍勃", "查理");
System.out.println("\n打印名字:");
// Lambda: name -> System.out.println("你好, " + name + "!")
// 对列表中的每个名字,执行打印问候语的操作
processListItems(names, name -> System.out.println("你好, " + name + "!"));
System.out.println("\n将名字转换为大写并打印 (仅打印,不修改原列表):");
// Lambda: name -> System.out.println(name.toUpperCase())
// 对列表中的每个名字,先转大写,然后打印
processListItems(names, name -> System.out.println(name.toUpperCase()));
// Consumer 也可以有副作用,比如修改外部状态 (通常需谨慎使用以避免复杂性)
StringBuilder allNames = new StringBuilder();
// Lambda: name -> allNames.append(name).append(" ")
// 这个 Consumer 修改了外部的 allNames 对象
processListItems(names, name -> allNames.append(name).append(" "));
System.out.println("\n拼接所有名字: " + allNames.toString().trim());
// List.forEach 的典型用法
System.out.println("\n使用 List.forEach 打印名字(大写):");
names.forEach(name -> System.out.println(name.toUpperCase())); // name -> System.out.println(...) 是一个Consumer
}
}
代码解读:
displayItem
方法接受一个项目和一个Consumer
,该Consumer
定义了如何“消费”或处理这个项目。processListItems
方法遍历列表,并对每个元素应用传入的Consumer
逻辑。这与List.forEach()
的行为非常相似。- 在
main
方法中:consolePrinter
和detailedPrinter
定义了不同的打印行为。System.out::println
是一个简洁的方法引用,用于直接打印。- 在处理
names
列表时,通过传递不同的Consumer
给processListItems
,实现了不同的处理逻辑(简单问候、转换为大写打印、追加到StringBuilder
)。 allNames.append(...)
的例子展示了Consumer
如何产生副作用(修改外部对象的状态)。虽然强大,但在复杂系统中应谨慎使用副作用,以保持代码的可预测性。names.forEach(...)
直接使用了List
接口内置的forEach
方法,该方法就接受一个Consumer
。
关键益处:
- 执行动作:非常适合表示对数据执行的无返回值的操作。
- 迭代与处理:与集合框架(如
List.forEach
)完美配合,简化迭代代码。 - 封装副作用:将有副作用的操作(如I/O、UI更新)封装到
Consumer
中,使得代码意图更清晰。