JAVA8学习总结——函数式编程与lambda表达式

来源:互联网 发布:网络填表终结者 编辑:程序博客网 时间:2024/06/05 18:24

       概述

       java8引入lambda表达式,提倡函数式编程。根据一些函数编程的定义指出,函数式编程是基于λ演算,而λ演算的一大特点就是以函数作为输入和输出。

         至于以函数作为输入和输出,在javaScript中体现得更明显一些,如下:

function function1(num1,num2){var reulst  = num1+num2;document.write(reulst);}function function2(fn){ //以函数fn作为输入return fn;  //以函数fn作为输出}var a  = function2(function1)a(2,3);  //调用时才执行两个数值的计算和document.write;


再看一下java8引入的lambda表达式的写法:

public class LambdaTest {public static void main(String[] args) {LambdaTest lambdaTest = new LambdaTest();String result = lambdaTest.composeStr("hello world",(value) -> value+"--你好,世界!!");System.out.println(result);}public String composeStr(String str,Function<String,String> function){   return function.apply(str);}}


          其中( (value) -> value+"--你好,世界!!")这一段即为lambda表达式。这段代码看起来很像一个函数,以“->”作为分割符,左边是输入参数,右边是函数的执行体。但是与上面的javaScript代码又有一些不一样,javaScript中的输入参数直接就是函数本身,而java的函数输入则依赖于一个接口,即Function。也就是说,在javaScript中,函数和变量是同等地位,即:参数fn可以是一个函数,也可以是一个变量值;而在java中则不是,需要依托Function这个接口,也就是人们常说的执行环境。在java中,当把这里的Function换成其他的类型的参数时,就无法执行了。这是由以前java版本决定的,以前的java版本必须以变量作为输入和返回,为了向后兼容,则提出了Function这个接口。


          在上面的概述中,提到了两个重要的内容:1、lambda表达式的语法;2、Functioni接口

          Lambda表达式的语法(java):

                    (param1,param2,param3) -> {...} //参数列表 -> 执行体

          Lambda表达式是一种匿名的函数,没有访问修饰符,返回值声明。

          在上面的java代码中,没有出现花括号{}。这里因为还有两个概念:1、表达式(expression);2、语句(statement)。

          如果执行体只有一行 ,称之为表达式。表达式执行有一个特点,就是一定有返回值,跟(a > b)这类的表达式一样。所以,上面的代码中的Lambda表达式中,没有return关键字;在语句的结尾也没有分号(;)。

          如果是多行,称之为语句。语句是没有返回值的,所以当有多行执行体的时候,必须有花括号{}。如果要有返回值,还必须在需要返回位置加上return关键字。如:{return a+b;}

          如果Lambda表达式中的参数列表只有一个,则可以不用写括号。

          如果Lambda表达式中的参数列表没有参数,则必须要有(),括号中没有参数。

          注:参数前面是可以指定类型的,JDK有类型推断,可以自动解析参数的类型。如果遇到无法解析的情况,则需要我们手动指定类型。如:(String param1,String param2)。指定类型之后,必须使用括号括起来。


          Function接口:

          这个接口有三个比较重要的特点:

                    1、有@functionalInterface注解

                    2、有一个apply方法

                    3、有compose和andThen两个默认方法,且有方法体。(java8中,接口中的方法可以有方法体;但必须用default关键字修饰)。


          @FunctionalInterface接口是一种契约,被其修饰的接口称为函数式接口。这类接口有两个共同的特征:

                    1、有且只有一个方法,该方法接口一个参数,并返回一个参数。

                    2、覆写Object类的公用方法的方法不能算作是该接口的函数方法,也就是说覆写toString()之类的方法,不影响函数接口的定义(只能有一个方法)。

          至于为什么会有这种契约,主要是因为java语言是一种以变量作为输入和输出的语言。要引入Lambda表达式和函数式编程,又要向后兼容以前的JDK版本。这种约束都是特定的含义的:

          首先,接收一个参数,并返回一个结果。这个特征是java中方法的典型特征。

          其次,有且只有一个方法。函数式编程强调以函数作为输入和输出,但是java语言并不具备这个能力。所以通过在接口中定义一个方法,且限制它只能有一个方法,这样就能将外部传入的方法体放入这个仅有的方法里面。这样做,既没有破坏java其他的语言特性,同时又能满足函数式编程的特征。这也就是上面说的,在java中,函数与变量并不是同等地位。java中的函数式编程需要依托Function这样的执行环境。

          再者,覆写Object类的公用方法,不算是接口默认的方法。在java中,Object是所有类的父类,所有类总都会有Object的公用方法,自然在函数式接口中也不会列外。所以覆写这些方法并不会影响接口中的有且仅有一个方法的特性。

          最后,对于被@FunctionInterface修饰的接口,称为函数式接口。没有被改注解修饰,且满足上面的两个特征的接口也算作是函数式接口。也就是说,在java8中,这种特征的接口被当成了一种默认的方式。这也就是为什么java8提倡函数式编程的原因吧。

         在JDK8中有很多这种接口,如:Predicate,Consumer  等等。


          函数式编程与Lambda表达式

          函数式编程有三个重要的特点:

                    1、惰性计算。

                    2、以函数作为输入和输出,传递的是行为 ,而不是值

                    3、引用透明,无状态。

                    4、高度抽象化。

先看一段代码:

public class NumberFunction {    public Integer calculateInteger(Integer number, Function<Integer,Integer> function){        return function.apply(number);    }}public class CalculateTest01 {    public static void main(String[] args) {        NumberFunction numberFunction = new NumberFunction();        System.out.println(numberFunction.calculateInteger(3,value -> value +1));    }}public class CalculateTest02 {    public static void main(String[] args) {        NumberFunction numberFunction = new NumberFunction();        System.out.println(numberFunction.calculateInteger(3,value -> value -1));    }}


          在上面的代码中,同一个类的同一个方法,因为传入的执行方式不一样而得出不一样的结果。一个加1,另一个则是减1.这也就体现了函数编程的两个特征(传递的是行为,而不是值和高度的抽象化)。换做以前的java代码,需要为“+”和“-”分别定义不同的方法。用两句话概括一下这里的抽象化:

                    1、calculateInteger的定义是:我需要对这个传入参数进行计算,怎么计算,由您决定。

                    2、如果单独做一个加法的方法,它的定义是:我需要对这个传入的值进行“加1”操作,你无法改变我的行为。

          很明显,函数式编程的做法显得更优雅一些,同时也可以看出,抽象程度更高。

          只有在方法被调用的时候,才进行值得运算,这也就是惰性计算。因为是惰性计算,调用时才进行计算,可以得到多种可能性的输出。而且,整个过程都是无状态的参数传递,没有保留任何的状态。正式由于这种无状态的透明引用,是的在上面的惰性计算中不会产生副作用。就像多线程编程中,都是讲变量声明在方法内部,方法结束即销毁,不保留状态,是影响范围变小。


以上仅为本人学习总结,如有不当之处,还望指正,谢谢!!!            


原创粉丝点击