151. Java Lambda 表达式 - 使用 Consumer<T> 接口处理对象
在 Java 中,Consumer<T> 是一个非常重要的功能接口,它接受一个输入参数,但不返回任何结果。与 Supplier<T>(它不接受参数,返回结果)不同,Consumer<T> 专注于对传入的对象执行操作,而不关心返回值。
Consumer<T> 接口的定义
Consumer<T> 接口只有一个抽象方法 accept(T t),用于接受一个类型为 T 的参数并进行处理:
java
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
示例:使用 Consumer<String> 打印字符串
java
Consumer<String> printer = s -> System.out.println(s);
这个 Consumer 实现接收一个字符串并将其打印到控制台。可以像这样使用它:
java
for (int i = 0; i < 5; i++) {
int nextRandom = newRandom.getAsInt();
printer.accept("next random = " + nextRandom);
}
在这个示例中,我们使用 printer 来打印每次生成的随机数。
使用专用的 Consumer
Consumer<T> 接口的实现是通用的,但有时候我们需要处理原始类型数据(例如 int、long、double),这时装箱(autoboxing)和拆箱(unboxing)会带来性能开销。为了优化性能,JDK 提供了专门的 Consumer 接口处理原始类型:IntConsumer、LongConsumer 和 DoubleConsumer。
示例:使用 IntConsumer 打印整数
java
Consumer<Integer> printer = i -> System.out.println(i);
这段代码使用 Consumer<Integer> 打印整数,但是,如果处理的是大量数据,自动装箱和拆箱可能会影响性能。因此,可以使用专用的 IntConsumer 接口来避免这种性能损失。
java
IntConsumer intPrinter = i -> System.out.println(i);
IntConsumer 的 accept() 方法直接接受 int 类型,而不会进行装箱,因此效率更高。
使用 BiConsumer<T, U> 处理两个参数
除了接受单个参数的 Consumer<T>,JDK 还提供了 BiConsumer<T, U> 接口,它可以同时接受两个参数,并对它们进行操作。它的定义如下:
java
@FunctionalInterface
public interface BiConsumer<T, U> {
void accept(T t, U u);
}
示例:使用 BiConsumer<Random, Integer> 打印多个随机数
java
BiConsumer<Random, Integer> randomNumberPrinter = (random, number) -> {
for (int i = 0; i < number; i++) {
System.out.println("next random = " + random.nextInt());
}
};
在上面的代码中,randomNumberPrinter 是一个 BiConsumer,它接受一个 Random 对象和一个整数 number,并打印出 number 个随机数。
调用这个 BiConsumer:
java
randomNumberPrinter.accept(new Random(314L), 5);
输出示例:
java
next random = 1
next random = 5
next random = 3
next random = 0
next random = 2
专用的 BiConsumer 接口
与 Consumer<T> 类似,BiConsumer<T, U> 也有专门的版本,处理原始类型数据,避免装箱和拆箱的开销。它们包括:
ObjIntConsumer<T>:接受一个对象和一个int。ObjLongConsumer<T>:接受一个对象和一个long。ObjDoubleConsumer<T>:接受一个对象和一个double。
这些专用接口与 BiConsumer 的工作方式相同,但它们通过避免不必要的装箱来提高性能。
将 Consumer 传递给 Iterable
Iterable 接口的 forEach() 方法是另一个与 Consumer<T> 紧密结合的地方。forEach() 方法接受一个 Consumer<T> 并对 Iterable 中的每个元素执行操作。这使得对集合的处理更加简洁和高效。
示例:使用 forEach() 遍历列表并打印元素
java
List<String> strings = List.of("Java", "Lambda", "Consumer");
Consumer<String> printer = s -> System.out.println(s);
strings.forEach(printer);
在这个例子中,strings.forEach(printer) 会遍历列表 strings,并将 printer Consumer 应用于每个元素,将其打印出来。
输出示例:
java
Java
Lambda
Consumer
forEach() 方法提供了一种简单的方式来处理 Iterable 中的每个元素。不再需要手动编写循环语句,代码更加简洁和易于理解。
总结
Consumer<T>接口 :Consumer<T>接口用于接受一个对象并对其执行操作,但不返回任何值。它通常用于打印、修改或消费数据。- 专用
Consumer接口 :为了避免装箱和拆箱带来的性能损失,JDK提供了专用的IntConsumer、LongConsumer和DoubleConsumer接口处理原始类型。 BiConsumer<T, U>接口 :BiConsumer接口允许同时处理两个参数,并执行操作。它在需要处理两个输入时非常有用。forEach()方法 :forEach()方法与Consumer<T>接口结合使用,提供了一种简单的方式来遍历Iterable并对每个元素执行操作。
通过理解并使用这些接口,可以在 Java 中编写更简洁、易读且高效的代码。