引入
在Java中,函数式接口是只包含一个抽象方法的接口。它们是支持Lambda表达式的基础,因为Lambda表达式需要一个目标类型,这个目标类型必须是一个函数式接口。
如果要用Lambda表达式,则左边必须对应着一个类型,而这个类型,一定是函数式接口!
如:MyFunctionalInterface myFunction = (a,b)->System.out.println(a+b);
。这是一个Lambda表达式,而MyFunctionalInterface
就是他对应的类型,这个类型一定是函数式接口!
而Function,是函数式接口的一个顶层定义。
函数式接口的出入参定义
Java中有非常多的内置FunctionAPI
。
看起来很多,常用的有按照出入参来分的四个大类加一个断言。
- 有入参,无出参【消费者】:
consumer.accept()
Consumer<String> consumer = (s)->System.out.println("consumer.accept: " + s); consumer.accept("Hello");
执行结果:
因为有入参,无出参,所以是消费掉参数。所以内置的方法是
void accept(T t)
,accept翻译过来就是接受的意思,所以我接受一个参数,然后吃掉~不给返回值。 - 有入参,有出参【多功能函数】:
function.apply()
Function<String,Integer> function = (s)->Integer.parseInt(s); System.out.println("function.apply: " + function.apply("123"));
执行结果:
因为有入参也有出参,所以内置的方法是
R apply(T t)
,apply翻译过来就是应用的意思,我接受你一个参数,去使用它,使用完了之后告诉你结果,给你返回值。 - 无入参,无出参【普通函数】
Runnable runnable = ()->System.out.println("runnable: " + "Hello runnable"); new Thread(runnable).start();
执行结果:
-
无入参,有出参【提供者】:
supplier.get()
Supplier<String> supplier = ()-> UUID.randomUUID().toString(); System.out.println("supplier.get: " + supplier.get());
执行结果:
因为没有入参,只有出参,所以内置方法是
T get()
,get就是拿,你从我这里拿。因为我是提供者,所以我得拿我自己的一个东西,然后分享给你~ - 【断言】:
Predicate<T>
Predicate<Integer> predicate = (t)-> t % 2 == 0; boolean a = predicate.test(6); // 正向判断 boolean b = predicate.negate().test(6); // 反向判断 System.out.println("predicate.test: " + a); System.out.println("predicate.negate().test: " + b);
执行结果:
boolean test(T t)
是正向判断,而negate()
是反向判断。
至于其他的内置函数也是同理。比如:
BiConsumer
的内置方法则是void accept(T t, U u);
,bi就是多路的意思,能接受两个入参;DoubleConsumer
的内置方法则是void accept(double value);
,能接受一个double
类型的入参;IntConsumer
的内置方法则是void accept(int value);
,能接受一个int
类型的入参;- 其他的
内置FunctionAPI
亦是同理~
把Function串联起来
知道了有这些内置的接口有什么用呢?怎么把这一系列的操作串联起来呢?
可以看看接下来的这个案例:
// Supplier 定义数据提供者函数
Supplier<String> supplier = () -> "42";
// Predicate 断言:验证是否为一个数字
Predicate<String> isNumber = s -> s.matches("-?\\d+(\\.\\d)?");
// Function 转换器:把字符串变成数字
Function<String,Integer> parseToInt = s -> Integer.parseInt(s);
// 简化写法 类::实例方法(或静态方法)
Function<String, Integer> parseToInt2 = Integer::parseInt;
// Comsumer 消费数据,打印数据
Consumer<Integer> consumer = num -> {
if (num % 2 == 0){
System.out.println(num + "是偶数");
}else {
System.out.println(num + "是奇数");
}
};
逻辑分解:
- 用
Supplier
构建一个提供者函数,里面装有一个字符串 - 用
Predicate
断言,可以判断入参的字符串是不是数字 - 用
Function
构建转换器,要把入参的字符串转换成数字,然后把数字出参作为出参返回出去 - 用
Consumer
构建一个消费者函数,把传进来的入参消费掉。不返回出参。
把上面操作串联起来:
// 串在一起,实现判断42这个字符串是奇数还是偶数
if (isNumber.test(supplier.get())){
// 说明是数字
consumer.accept(parseToInt.apply(supplier.get()));
}else {
// 说明不是数字
System.out.println("非法的数字");
}
这段代码是不是看起来很乱很复杂?
接下来让我们把代码逻辑一层一层地分解一下:
supplier.get()
,这是通过get()
方法把Supplier
里面的参数拿出来。-
isNumber.test()
,调用断言的test()
方法,判断这个参数是否为数字(正向判断)。isNumber.test(supplier.get())
,通过断言判断Supplier
里的参数是不是数字。如果是:返回true;如果不是:返回false。如果
Supplier
里的参数是数字,则进入到if语句
里面。 -
parseToInt.apply(supplier.get())
,supplier.get()
是一个字符串,parseToInt.apply()
的作用就是把这个字符串转成数字 -
consumer.accept()
,接收一个参数,并且内部消费掉,不返回出参。而他的食物,就是前面的parseToInt.apply(supplier.get())
,要接收这个数字,判断他是奇数还是偶数。
例如,
当我的supplier
中的字符串是"42"
时,
串联起来后输出的是:
而当supplier
中的字符串是"42a"
时,
串联起来后输出的则是:
以上就是Function函数式的各种写法。这里可能有人会有疑问:
Lambda表达式和Function函数式虽然很简洁,但需要调用多层函数的时候,易读性就会变得非常差。那为什么还要去用呢?为什么还要去学呢?
我个人的见解是:
多层嵌套的时候,代码会变得繁琐,不易于阅读。这也代表着别人维护起来也会变得困难。想一想,如果别人都不熟悉,只有你熟悉的话,这是不是也是一种防御性编程呢~
虽然Lambda表达式和Function函数式多层嵌套会变得繁琐、不利于阅读,
但当一些简单的场景时,利用Lambda表达式看起来会非常的简洁舒服,随之代码也会变得优雅~