什么是函数式编程,我们由一段代码来引出
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);其中
假如我们要计算表达式:(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对象
.filter(f->f<=3) // filter返回Stream对象
.max((o1,o2)->o1-o2);//max终止操作,返回Optional
System.out.println(result.get());
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,看下边的例子:
JDK源码 java.util.function包下面提供的一系列的预置的函数式接口定义
接口类 | 功能描述 |
---|---|
Runnable | 直接执行一段处理函数,无任何输出参数,也没有任何输出结果。 |
Supplier | 执行一段处理函数,无任务输入参数,返回一个T类型的结果。与Runnable的区别在于Supplier执行完之后有返回值。 |
Consumer | 执行一段处理函数,支持传入一个T类型的参数,执行完没有任何返回值。 |
BiConsumer |
与Consumer类型相似,区别点在于BiConsumer支持传入两个不同类型的参数,执行完成之后依旧没有任何返回值。 |
Function |
执行一段处理函数,支持传入一个T类型的参数,执行完成之后,返回一个R类型的结果。与Consumer的区别点就在于Function执行完成之后有输出值。 |
BiFunction |
与Function相似,区别点在于BiFunction可以传入两个不同类型的参数,执行之后可以返回一个结果。与BiConsumer也很类似,区别点在于BiFunction可以有返回值。 |
UnaryOperator | 传入一个参数对象T,允许对此参数进行处理,处理完成后返回同样类型的结果对象T。继承Function接口实现,输入输出对象的类型相同。 |
BinaryOperator | 允许传入2个相同类型的参数,可以对参数进行处理,最后返回一个仍是相同类型的结果T。继承BiFunction接口实现,两个输入参数以及最终输出结果的对象类型都相同。 |
Predicate | 支持传入一个T类型的参数,执行一段处理函数,最后返回一个布尔类型的结果。 |
BiPredicate |
支持传入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;
});
其实上边的函数,也可以换成接口实现类的方式,这个可以自己在开发过程中进行评估,函数式有自己的高效,如果逻辑不多的话,可以使用函数式接口,因为不用再创建太多的类,如果逻辑复杂点的话,可以再创建接口和对应的实现。
函数式接口算是在程序运行过程中对接口进行回调
。