函数式编程总结与应用

前言

什么是函数式编程,我们由一段代码来引出

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对象
            .filter(f->f<=3) // filter返回Stream对象
            .max((o1,o2)->o1-o2);//max终止操作,返回Optional
        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,看下边的例子:

如果是类成员变量的话,没有问题
函数式编程总结与应用_第1张图片

函数接口

特征

  1. interface接口类
  2. 类中有且仅有1个public类型的接口方法;
  3. @FunctionalInterface注解标识可选,加上会提高可读性。

JDK1.8的支持

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的参数,执行一段处理函数,最后返回一个布尔类型的结果。

函数式接口应用

比如我们现在需要统计系统的资源利用率,但是数据的来源不一样,获取到数据后进行组装,然后入库,大致的流程如下:

函数式编程总结与应用_第2张图片

主流程
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;
        });
总结
  • 其实上边的函数,也可以换成接口实现类的方式,这个可以自己在开发过程中进行评估,函数式有自己的高效,如果逻辑不多的话,可以使用函数式接口,因为不用再创建太多的类,如果逻辑复杂点的话,可以再创建接口和对应的实现。

  • 函数式接口算是在程序运行过程中对接口进行回调

你可能感兴趣的:(设计模式系列,函数式编程)