函数编程在C#、Python、JavaScript中都得到充分体现。而Java直到最新的Java 8才开始正式支持函数编程,最明显的改进就是对Lamba表达式的支持。正如C#之父Anders Hejlsberg在那篇文章 编程语言大趋势 中所讲,未来的编程语言将逐渐融合各自的特性,而不存在单纯的声明式语言(如之前的Java)或者单纯的函数编程语言。将来声明式编程语言借鉴函数编程思想,函数编程语言融合声明式编程特性...这几乎是一种必然趋势。如下图所示:

影响力较大的三个趋势
那具体而言我们为什么需要Lambda表达式呢?难道Java的OO和命令式编程(imperative programming)特性不够强大吗?下面让我们来分析下其原因。
1、内部循环和外部循环
先看一个大家耳熟能详的例子:
1 | List<Integer> numbers = Arrays.asList( 1 , 2 , 3 , 4 , 5 , 6 ); |
3 | for ( int number : numbers) { |
4 | System.out.println(number); |
是不是很常见呢?这个叫外部循环(External Iteration)。但是外部循环有什么问题呢?简单来说存在下面三个缺点:
- 只能顺序处理List中的元素(process one by one)
- 不能充分利用多核CPU
- 不利于编译器优化
而如果利用内部循环,代码写成下面这样:
1 | List<Integer> numbers = Arrays.asList( 1 , 2 , 3 , 4 , 5 , 6 ); |
3 | numbers.forEach((Integer value) -> System.out.println(value)); |
这样就能规避上面的三个问题:
- 不一定需要顺序处理List中的元素,顺序可以不确定
- 可以并行处理,充分利用多核CPU的优势
- 有利于JIT编译器对代码进行优化
类似的C#从4.0版本开始也支持集合元素并行处理,代码如下:
1 | List< int > nums = new List< int > { 1, 2, 3, 4, 5, 6 }; |
2 | Parallel.ForEach(nums, (value) => |
4 | Console.WriteLine(value); |
2、传递行为,而不仅仅是传值
如果你使用C#有一段时间的话,那么你很可能已经明白这个标题的意思了。在C#中,经常看到一些函数的参数是Action或者Func类型,比如下面这个:
01 | public class ArticleDac { |
03 | public Article GetArticles(Func<IDbSet<Article>, Article> func) |
06 | return func(db.Articles); |
13 | var firstArticle = new ArticleDac().GetArticles( |
15 | articleDbSet.AsQueryable().FirstOrDefault(x => x.id == articleId) |
看不懂?没关系。我们先来看一个体现传值局限性的场景吧,上代码:
1 | List<Integer> numbers = Arrays.asList( 1 , 2 , 3 , 4 , 5 , 6 ); |
3 | public int sumAll(List<Integer> numbers) { |
5 | for ( int number : numbers) { |
sumAll算法很简单,完成的是将List中所有元素相加。某一天如果我们需要增加一个对List中所有偶数求和的方法sumAllEven,如下:
1 | public int sumAllEven(List<Integer> numbers) { |
3 | for ( int number : numbers) { |
又有一天,我们需要增加第三个方法:对List中所有大于3的元素求和,那是不是继续加下面的方法呢?
1 | public int sumAllEven(List<Integer> numbers) { |
3 | for ( int number : numbers) { |
比较这三个方法,我们发现了一个很明显的“代码臭味”—— 代码重复(详情参考《重构》),三个方法的唯一区别在于if判断这一行代码。如果脱离这里的上下文,我们会怎么做呢?我首先会先想到利用策略模式重构代码如下:
01 | public interface Strategy { |
02 | public boolean test( int num); |
05 | public class SumAllStrategy implements Strategy { |
06 | public boolean test( int num) { |
11 | public class SumAllEvenStrategy implements Strategy { |
12 | public boolean test( int num) { |
17 | public class ContextClass { |
18 | private Strategy stragegy = null ; |
19 | private final static Strategy DEFAULT_STRATEGY = new SumAllStrategy(); |
21 | public ContextClass() { |
25 | public ContextClass(Stragegy stragegy) { |
26 | if (strategy != null ) { |
27 | this .strategy = strategy; |
30 | this .strategy = DEFAULT_STRATEGY; |
34 | public int sumAll(List<Integer> numbers) { |
36 | for ( int number : numbers) { |
37 | if (strategy.test(number)) { |
48 | ContextClass context = new ContextClass(); |
49 | context.sumAll(numbers); |
设计模式在这里发挥了作用,OO特性还是蛮强大的!但这是唯一的解决方案吗(当然不考虑用其他设计模式来解决,因为都是OO范畴!)?当然有,该轮到Java 8 Lambda表达式中的谓词(Predicate)发挥作用了!
01 | public int sumAll(List<Integer> numbers, Predicate<Integer> p) { |
03 | for ( int number : numbers) { |
11 | sumAll(numbers, n -> true ); |
12 | sumAll(numbers, n -> n % 2 == 0 ); |
13 | sumAll(numbers, n -> n > 3 ); |
代码是不是比上面简洁很多了?语义应该也很明确,就不多解释了,如果实在看不懂,请参考我的另外一篇文章:
http://www.cnblogs.com/feichexia/archive/2012/11/15/Java8_LambdaExpression.html 从这里也可以看出未引入Lambda表达式之前的Java代码的冗长(Java这点被很多人诟病)。
当然C#早已经支持这种用法,用C#改写上面的代码如下:
1 | public int SumAll(IEnumerable< int > numbers, Predicate< int > predicate) { |
2 | return numbers.Where(i => predicate(i)).Sum(); |
5 | SumAll(numbers, n => true ); |
6 | SumAll(numbers, n => n % 2 == 0); |
7 | SumAll(numbers, n => n > 3); |
|