概述
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”操作,你无法改变我的行为。
很明显,函数式编程的做法显得更优雅一些,同时也可以看出,抽象程度更高。
只有在方法被调用的时候,才进行值得运算,这也就是惰性计算。因为是惰性计算,调用时才进行计算,可以得到多种可能性的输出。而且,整个过程都是无状态的参数传递,没有保留任何的状态。正式由于这种无状态的透明引用,是的在上面的惰性计算中不会产生副作用。就像多线程编程中,都是讲变量声明在方法内部,方法结束即销毁,不保留状态,是影响范围变小。
以上仅为本人学习总结,如有不当之处,还望指正,谢谢!!!