Java 8 之 函数式编程与Lambda表达式 入门到深入 (一)

前言

最近看《Effective Java》里面提到了很多关于Java 8的特性,听的最多的也是Lambda、函数式编程。看了很多文章和资料,现做一个总结。

本文以是什么,为什么,怎么使用为主线,详细介绍Java 8新特性之函数式编程,争取在读者读完本文后,对函数式编程有一个清楚的认识。

函数式编程是什么

1. 引入:函数式编程

情景A:
设计一个工具类,这个类包含两个方法,一个是能求两个参数之间的和,一个是两个参数之间的差

//求和
public static int getSum(int begin,int end){
    assert(begin<end);
    int sum=begin;
    for(int i=begin+1;i<end;i++){
       sum+=i;  
    }
    return sum;
}

//求积
public static int getPro(int begin,int end){
    assert(begin<end);
    int pro=begin;
    for(int i=begin+1;i<end;i++){
        pro*=i;
    }
    return pro;
}

仔细看以上代码,我们发现两个方法之间,真正不同的,只有一行代码!
读者不妨思考下,如果是你,应该怎么优化上面的代码?
看以下代码:

//通用方法
public static int commonOperate(BinaryOperator<Integer> operator, int begin, int end) {
    assert (begin < end);
    int result = begin;
    for (int i = begin + 1; i < end; i++) {
        result = operator.apply(i, result);
    }
    return result;
}

//求和
public static int getSum(int begin,int end){
    return commonOperate(Integer::sum,begin,end);
}

//求积
public static int getPro(int begin,int end){
    return commonOperate((x,y)->x*y,begin,end);
}

是的,没有任何重复的代码,并且这样的代码,对以后的方法扩展很方便,我们只需要修改传入的lambda方法即可。
在没有事先了解什么是lambda和函数式编程之前,可能会看不懂以上的代码,下面解释一下上面的代码,让读者体会lambda和函数式编程的神奇之处。

  • 在代码没有优化之前,我们发现两个方法仅仅在于sum+=ipro*=i不同,而类似对于sumpro的具体操作,我们称为方法
  • 假设我们能将方法(function)作为一个参数传递进去,那么问题便迎刃而解。
    sum=function(sum,i); //具体的行为,根据传入的function而定。
  • 在优化后的代码中,我们便传入了一个函数式参数(也就是lambda),用来控制对结果的操作。

也就是说:在Java 8 中,允许使用函数式编程,也就是允许将函数(方法)作为一个变量传递到其他的方法中,通过上面的优化就能看见具体的应用场景。

如果你学过C++,那么直接参考传递函数指针即可。

那这和lambda表达式又有什么联系呢?

Lambda 表达式”(lambda expression)是一个匿名函数 —百度百科

看到lambda的定义,我们很自然就能和匿名类联系起来,匿名类就是一个只使用一次,并且没有名字的类。lambda表达式便是这样的函数。
再回到上面的代码,如果没有lambda表达式。那么我们的代码是这样的:

//求积
public static int getPro(int begin,int end){
    int product(int x,int y){return x*y;}  //这是错误的语法,仅仅用来举例
    return commonOperate(product,begin,end);
}

这样的语句看起来混乱,而且远没有lambda简洁,可读。

总结: lambda 表达式是为了和函数式编程相辅相成,函数式编程意味着可以将函数作为一个参数传入方法中,lambda表达式便是在传参时所定义的匿名函数。


官方定义

  1. 在函数式编程中,函数也被归纳为第一等公民

    一等公民的定义在《C++ primer》中提到过,指的是像基本数据类型一样能够被定义,传递,赋值等。

  2. Java 中,要求函数式编程中的函数必须为纯函数
    纯函数指的是:

    • 不依赖外部系统状态,任何时候,只要输入一样,输出总是不变
    • 函数的执行不影响外部程序的状态。(类似不改变成员变量,全局b变量等)

在这里顺便提函数编程的优点:

  1. 可以利用Memoization技术提升性能
    Memoization技术指的是在第一次计算了一个输入的结果以后,下次遇到相同的输入,就直接放回结果,免去了再次计算的过程,能够提升程序性能。而这一点利用的是纯函数的输入相同,输出必然相同的特点。

    有点像Java的自动装箱

  2. 可以延迟求值(Lazy Evaluation)
    延迟求值指的是表达式在真正被使用的时候才进行求值。
    比如:

    void test(boolean flag,String str){
       if(flag){
           System.out.println(str);
       }
    }
    
    test(false,"1"+"2"+"3");
    

    在延迟求值的情况下,"1"+"2"+"3"是不会被计算的。

    有点类似逻辑运算的运算短路的情况

为什么要使用函数式编程

参考资料:
可能是最好的函数式编程入门
Java8 学习笔记
Java 8中一些常用的全新的函数式接口
函数式编程初探