12.3 常用函数式接口

  JDK提供了大量常用的函数式接口以丰富Lambda的典型使用场景,它们主要在java.util.function 包中被提供。下面是最简单的几个接口及使用示例。

12.3.1 Supplier接口

  java.util.function.Supplier<T>接口仅包含一个无参的方法:T get()。用来获取一个泛型参数指定类型的对象数据。由于这是一个函数式接口,这也就意味着对应的Lambda表达式需要“对外提供”一个符合泛型类型的对象数据。

1
2
3
4
5
6
7
8
9
10
11
12
import java.util.function.Supplier;

public class Demo08Supplier {
private static String getString(Supplier<String> function) {
return function.get();
}
public static void main(String[] args) {
String msgA = "Hello";
String msgB = "World";
System.out.println(getString(() ‐> msgA + msgB));
}
}

12.3.2 练习:求数组元素最大值

题目

  使用Supplier接口作为方法参数类型,通过Lambda表达式求出int数组中的最大值。提示:接口的泛型请使用java.lang.Integer类。

解答

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Demo02Test {
//定一个方法,方法的参数传递Supplier,泛型使用Integer
public static int getMax(Supplier<Integer> sup){
return sup.get();
}
public static void main(String[] args) {
int arr[] = {2,3,4,52,333,23};
//调用getMax方法,参数传递Lambda
int maxNum = getMax(()‐>{
//计算数组的最大值
int max = arr[0];
for(int i : arr){
if(i>max){
max = i;
}
}
return max;
});
System.out.println(maxNum);
}
}

12.3.3 Consumer接口

  java.util.function.Consumer<T>接口则正好与Supplier接口相反,它不是生产一个数据,而是消费一个数据,其数据类型由泛型决定。

抽象方法:accept

  Consumer接口中包含抽象方法void accept(T t),意为消费一个指定泛型的数据。基本使用如:

1
2
3
4
5
6
7
8
9
10
import java.util.function.Consumer;

public class Demo09Consumer {
private static void consumeString(Consumer<String> function) {
function.accept("Hello");
}
public static void main(String[] args) {
consumeString(s ‐> System.out.println(s));
}
}

当然,更好的写法是使用方法引用。

默认方法:andThen

  如果一个方法的参数和返回值全都是Consumer 类型,那么就可以实现效果:消费数据的时候,首先做一个操作,然后再做一个操作,实现组合。而这个方法就是Consumer 接口中的default方法andThen 。下面是JDK的源代码:

1
2
3
4
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) ‐> { accept(t); after.accept(t); };
}

备注:
  java.util.ObjectsrequireNonNull()静态方法将会在参数为null时主动抛出NullPointerException异常。这省去了重复编写if语句和抛出空指针异常的麻烦。

  要想实现组合,需要两个或多个Lambda表达式即可,而andThen 的语义正是“一步接一步”操作。例如两个步骤组合的情况:

1
2
3
4
5
6
7
8
9
10
11
import java.util.function.Consumer;
public class Demo10ConsumerAndThen {
private static void consumeString(Consumer<String> one, Consumer<String> two) {
one.andThen(two).accept("Hello");
}
public static void main(String[] args) {
consumeString(
s ‐> System.out.println(s.toUpperCase()),
s ‐> System.out.println(s.toLowerCase()));
}
}

  运行结果将会首先打印完全大写的HELLO,然后打印完全小写的hello。当然,通过链式写法可以实现更多步骤的组合。

12.3.4 练习:格式化打印信息

题目

  下面的字符串数组当中存有多条信息,请按照格式“ 姓名:XX。性别:XX。”的格式将信息打印出来。要求将打印姓名的动作作为第一个Consumer 接口的Lambda实例,将打印性别的动作作为第二个Consumer 接口的Lambda实例,将两个Consumer 接口按照顺序“拼接”到一起。

1
2
3
public static void main(String[] args) {
String[] array = { "迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男" };
}

解答

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.util.function.Consumer;
public class DemoConsumer {
public static void main(String[] args) {
String[] array = { "迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男" };
printInfo(s ‐> System.out.print("姓名:" + s.split(",")[0]),
s ‐> System.out.println("。性别:"
+ s.split(",")[1] + "。"),
array);
}
private static void printInfo(Consumer<String> one,
Consumer<String> two, String[] array) {
for (String info : array) {
one.andThen(two).accept(info); // 姓名:迪丽热巴。性别:女。
}
}
}