前言
最近看《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+=i
和pro*=i
不同,而类似对于sum
和pro
的具体操作,我们称为方法。 - 假设我们能将方法(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表达式便是在传参时所定义的匿名函数。
官方定义
- 在函数式编程中,函数也被归纳为第一等公民
一等公民的定义在《C++ primer》中提到过,指的是像基本数据类型一样能够被定义,传递,赋值等。
- Java 中,要求函数式编程中的函数必须为纯函数
纯函数指的是:- 不依赖外部系统状态,任何时候,只要输入一样,输出总是不变
- 函数的执行不影响外部程序的状态。(类似不改变成员变量,全局b变量等)
在这里顺便提函数编程的优点:
- 可以利用Memoization技术提升性能
Memoization技术指的是在第一次计算了一个输入的结果以后,下次遇到相同的输入,就直接放回结果,免去了再次计算的过程,能够提升程序性能。而这一点利用的是纯函数的输入相同,输出必然相同的特点。有点像Java的自动装箱
- 可以延迟求值(Lazy Evaluation)
延迟求值指的是表达式在真正被使用的时候才进行求值。
比如:void test(boolean flag,String str){ if(flag){ System.out.println(str); } } test(false,"1"+"2"+"3");
在延迟求值的情况下,
"1"+"2"+"3"
是不会被计算的。有点类似逻辑运算的运算短路的情况