原文转载自http://www.cnblogs.com/snowInPluto/p/5981400.html
1.1 函数式编程简介
我们最常用的面向对象编程(Java)属于命令式编程(Imperative Programming)这种编程范式。常见的编程范式还有逻辑式编程(Logic Programming),函数式编程(Functional Programming)。
函数式编程作为一种编程范式,在科学领域,是一种编写计算机程序数据结构和元素的方式,它把计算过程当做是数学函数的求值,而避免更改状态和可变数据。
函数式编程并非近几年的新技术或新思维,距离它诞生已有大概50多年的时间了。它一直不是主流的编程思维,但在众多的所谓顶级编程高手的科学工作者间,函数式编程是十分盛行的。
什么是函数式编程?简单的回答:一切都是数学函数。函数式编程语言里也可以有对象,但通常这些对象都是恒定不变的 —— 要么是函数参数,要什么是函数返回值。函数式编程语言里没有 for/next 循环,因为这些逻辑意味着有状态的改变。相替代的是,这种循环逻辑在函数式编程语言里是通过递归、把函数当成参数传递的方式实现的。
举个例子:
a = a + 1
这段代码在普通成员看来并没有什么问题,但在数学家看来确实不成立的,因为它意味着变量值得改变。
1.2 Lambda 表达式简介
Java 8的最大变化是引入了Lambda(Lambda 是希腊字母 λ 的英文名称)表达式——一种紧凑的、传递行为的方式。
先看个例子:
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
System.out.println("button clicked");
}
});
这段代码使用了匿名类。ActionListener
是一个接口,这里 new 了一个类实现了 ActionListener
接口,然后重写了 actionPerformed
方法。actionPerformed
方法接收 ActionEvent
类型参数,返回空。
这段代码我们其实只关心中间打印的语句,其他都是多余的。所以使用 Lambda 表达式,我们就可以简写为:
button.addActionListener(event -> System.out.println("button clicked"));
2. Lambda 表达式
2.1 Lambda 表达式的形式
Java 中 Lambda 表达式一共有五种基本形式,具体如下:
➊
Runnable noArguments = () -> System.out.println("Hello World");
➋
ActionListener oneArgument = event -> System.out.println("button clicked");
➌
Runnable multiStatement = () -> {
System.out.print("Hello");
System.out.println(" World");
};
➍
BinaryOperator<Long> add = (x, y) -> x + y;
➎
BinaryOperator<Long> addExplicit = (Long x, Long y) -> x + y;
➊中所示的 Lambda 表达式不包含参数,使用空括号 () 表示没有参数。该 Lambda 表达式 实现了 Runnable 接口,该接口也只有一个 run 方法,没有参数,且返回类型为 void。➋中所示的 Lambda 表达式包含且只包含一个参数,可省略参数的括号,这和例 2-2 中的 形式一样。Lambda 表达式的主体不仅可以是一个表达式,而且也可以是一段代码块,使用大括号 ({})将代码块括起来,如➌所示。该代码块和普通方法遵循的规则别无二致,可以用返 回或抛出异常来退出。只有一行代码的 Lambda 表达式也可使用大括号,用以明确 Lambda表达式从何处开始、到哪里结束。Lambda 表达式也可以表示包含多个参数的方法,如➍所示。这时就有必要思考怎样去阅 读该 Lambda 表达式。这行代码并不是将两个数字相加,而是创建了一个函数,用来计算 两个数字相加的结果。变量 add 的类型是 BinaryOperator
记住一点很重要,Lambda 表达式都可以扩写为原始的“匿名类”形式。所以当你觉得这个 Lambda 表达式很复杂不容易理解的时候,不妨把它扩写为“匿名类”形式来看。
2.2 闭包
如果你以前使用过匿名内部类,也许遇到过这样的问题。当你需要匿名内部类所在方法里的变量,必须把该变量声明为 final
。如下例子所示:
final String name = getUserName();
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
System.out.println("hi " + name);
}
});
Java 8放松了这一限制,可以不必再把变量声明为 final
,但其实该变量实际上仍然是 final
的。虽然无需将变量声明为 final,但在 Lambda 表达式中,也无法用作非终态变量。如果坚持用作非终态变量(即改变变量的值),编译器就会报错。
2.3 函数接口
上面例子里提到了 ActionListener
接口,我们看一下它的代码:
public interface ActionListener extends EventListener {
/**
* Invoked when an action occurs.
*/
public void actionPerformed(ActionEvent e);
}
ActionListener
只有一个抽象方法:actionPerformed
,被用来表示行为:接受一个参数,返回空。记住,由于 actionPerformed
定义在一个接口里,因此 abstract
关键字不是必需的。该接口也继承自一个不具有任何方法的父接口:EventListener
。
我们把这种接口就叫做函数接口。
JDK 8 中提供了一组常用的核心函数接口:
接口 | 参数 | 返回类型 | 描述 |
---|---|---|---|
Predicate<T> | T | boolean | 用于判别一个对象。比如求一个人是否为男性 |
Consumer<T> | T | void | 用于接收一个对象进行处理但没有返回,比如接收一个人并打印他的名字 |
Function<T, R> | T | R | 转换一个对象为不同类型的对象 |
Supplier<T> | None | T | 提供一个对象 |
UnaryOperator<T> | T | T | 接收对象并返回同类型的对象 |
BinaryOperator<T> | (T, T) | T | 接收两个同类型的对象,并返回一个原类型对象 |
其中 Cosumer
与 Supplier
对应,一个是消费者,一个是提供者。
Predicate
用于判断对象是否符合某个条件,经常被用来过滤对象。
Function
是将一个对象转换为另一个对象,比如说要装箱或者拆箱某个对象。
UnaryOperator
接收和返回同类型对象,一般用于对对象修改属性。BinaryOperator
则可以理解为合并对象。
如果以前接触过一些其他 Java 框架,比如 Google Guava,可能已经使用过这些接口,对这些东西并不陌生。所以,其实 Java 8 的改进并不是闭门造车,而是集百家之长。
3. 集合处理
3.1 Stream 简介
在程序编写过程中,集合的处理应该是很普遍的。Java 8 对于 Collection
的处理花了很大的功夫,如果从 JDK 7 过渡到 JDK 8,这一块也可能是我们感受最为明显的。
Java 8 中,引入了流(Stream)的概念,这个流和以前我们使用的 IO 中的流并不太相同。
所有继承自 Collection
的接口都可以转换为 Stream
。还是看一个例子。
假设我们有一个 List
包含一系列的 Person
,Person
有姓名 name
和年龄 age
连个字段。现要求这个列表中年龄大于 20 的人数。
通常按照以前我们可能会这么写:
long count = 0;
for (Person p : persons) {
if (p.getAge() > 20) {
count ++;
}
}
但如果使用 stream
的话,则会简单很多:
long count = persons.stream()
.filter(person -> person.getAge() > 20)
.count();
这只是 stream
的很简单的一个用法。现在链式调用方法算是一个主流,这样写也更利于阅读和理解编写者的意图,一步方法做一件事。