前言
什么是函数式编程,我们由一段代码来引出
Optional<Integer> result = Stream.of("foo", "bar", "hello")
.map(s->s.length())
.filter(f->f<=3)
.max((o1,o2)->o1-o2);
System.out.println(result.get());
这段代码的作用是过滤出字符长度小于3的字符串,并计算出最大的字符串长度。java为函数式编程引入了3个新的语法概念:Stream类、Lambda表达式和函数接口(functional interface);其中
- Stream类的作用是通过它支持用“.”级联多个函数操作的代码编写方式;
- Lambda表达式的作用是简化代码的编写;
- 函数接口的作用是让我们可以把函数包裹成函数接口,把函数当作参数一样使用。
Stream类
假如我们要计算表达式:(3-1)x2+5。如果按我们知道的函数来编写代码,你会这样写:
add(multiply(subtract(3,1),2),5)
可读性不是太好,可以换个更好的写法,如下:
subtract(3,1).multiply(2).add(5)
我们知道java中的.代表的是调用关系,即某个对象调用了某个方法。为了支持上面这种级联调用,我们让每个函数返回一个通用类型,Stream类对象。中间操作返回的仍是Stream对象,终止操作返回确定的结果值。
再看下我们上边的方法
Optional<Integer> result = Stream.of("foo", "bar", "hello")
.map(s->s.length())// map返回Stream<Integer>对象
.filter(f->f<=3) // filter返回Stream<Integer>对象
.max((o1,o2)->o1-o2);//max终止操作,返回Optional<Integer>
System.out.println(result.get());
Lambda表达式
Lambda表达式的作用是简化代码的编写,是函数式编程的核心,表达式即匿名内部类。以上边的代码举个例子,比如用map函数的使用方式:
Stream.of("foo", "bar", "hello")
.map(new Function<String, Integer>() {
@Override
public Integer apply(String s) {
return s.length();
}
});
简化后的代码就是
Stream.of("foo", "bar", "hello").map(s->s.length())
Lambda表达式主要有三部分:输入,函数体和输出,标准写法如下:
(a,b) -> {语句1;语句2;return 输出;} //a和b都是输入参数,
还可以简化,如果入参只有一个,那么可以省略(),直接写成“a->{…}”;如果没有入参,可以直接保留函数体;
还有一点需要注意:java8会自动地将在Lambda表达式中使用的局部变量
视为final,看下边的例子:
如果是类成员变量的话,没有问题
函数接口
特征
- interface接口类
- 类中有且仅有1个public类型的接口方法;
- @FunctionalInterface注解标识可选,加上会提高可读性。
JDK1.8的支持
JDK源码 java.util.function包下面提供的一系列的预置的函数式接口定义
接口类 | 功能描述 |
---|---|
Runnable | 直接执行一段处理函数,无任何输出参数,也没有任何输出结果。 |
Supplier | 执行一段处理函数,无任务输入参数,返回一个T类型的结果。与Runnable的区别在于Supplier执行完之后有返回值。 |
Consumer | 执行一段处理函数,支持传入一个T类型的参数,执行完没有任何返回值。 |
BiConsumer<T, U> | 与Consumer类型相似,区别点在于BiConsumer支持传入两个不同类型的参数,执行完成之后依旧没有任何返回值。 |
Function<T, R> | 执行一段处理函数,支持传入一个T类型的参数,执行完成之后,返回一个R类型的结果。与Consumer的区别点就在于Function执行完成之后有输出值。 |
BiFunction<T, U, R> | 与Function相似,区别点在于BiFunction可以传入两个不同类型的参数,执行之后可以返回一个结果。与BiConsumer也很类似,区别点在于BiFunction可以有返回值。 |
UnaryOperator | 传入一个参数对象T,允许对此参数进行处理,处理完成后返回同样类型的结果对象T。继承Function接口实现,输入输出对象的类型相同。 |
BinaryOperator | 允许传入2个相同类型的参数,可以对参数进行处理,最后返回一个仍是相同类型的结果T。继承BiFunction接口实现,两个输入参数以及最终输出结果的对象类型都相同。 |
Predicate | 支持传入一个T类型的参数,执行一段处理函数,最后返回一个布尔类型的结果。 |
BiPredicate<T, U> | 支持传入2个相同类型T的参数,执行一段处理函数,最后返回一个布尔类型的结果。 |
函数式接口应用
比如我们现在需要统计系统的资源利用率,但是数据的来源不一样,获取到数据后进行组装,然后入库,大致的流程如下:
主流程
public void calculateResourceUsage(int type, ResourceObtain<T> resourceObtain) {
// 调用函数式接口获取源数据
List<ResourceUsage> list = resourceObtain.obtainResource(type);
// 组装list
buildData(list);
// 保存list
saveData(list);
}
函数式接口
@FunctionalInterface
public interface ResourceObtain<T> {
List<T> computePrice(int type);
}
调用
比如从平台1上边获取数据
resourceUsageService.calculateResourceUsage(1,x ->{
//调用平台1接口获取平台1的数据
List<ResourceUsage> list = rpc1.get();
return list;
});
获取平台2数据
resourceUsageService.calculateResourceUsage(2,x ->{
//调用平台1接口获取平台1的数据
List<ResourceUsage> list = rpc2.get();
return list;
});
总结
-
其实上边的函数,也可以换成接口实现类的方式,这个可以自己在开发过程中进行评估,函数式有自己的高效,如果逻辑不多的话,可以使用函数式接口,因为不用再创建太多的类,如果逻辑复杂点的话,可以再创建接口和对应的实现。
-
函数式接口算是在程序运行过程中对接口进行
回调
。