本文整理下Java8引入的最重要的函数式编程的特征 — lambda表达式,能让代码更加简单易懂。
Java中lambda表达式的关键特征:
- 是一个带参数的代码块,一般用于代码块的延后执行
- 可以转化为函数接口
- 能访问作用域内的不可变变量(final variables),这也是闭包的重要特征
- 支持函数引用和构造函数引用
- 接口支持实现默认方法和静态方法,多个接口的相同签名的默认方法,需要自己来解决冲突
在Lambda支持之前的代码块方式
在lambda支持之前,需要 显式定义类型 或者 使用匿名类 来支持代码块,下面展示一些例子。
线程任务
class Worker implements Runnable {
@Override
public void run() {
for (int i = 0; i < 1000; i++)
doWork();
}
}
Worker w = new Worker();
new Thread(w).start();
数组排序
class LengthComparator implements Comparator<String> {
@Override
public int compare(String first, String second) {
return Integer.compare(first.length(), second.length());
}
}
Arrays.sort(strings, new LengthComparator());
匿名类
strings.sort(new Comparator<String>() {
@Override
public int compare(String first, String second) {
return Integer.compare(first.length(), second.length());
}
});
Lambda表达式语法
基本语法:(parameters) -> { statements; }
不需要指定返回类型,返回类型,能从上下文自动推断;同时,函数体如果有多个分支(如if),需要每个分支都有return值。
表达式单句形式
如果函数体只有一句,可以直接写该表达式,不用写return和大括号。
(String first, String second)
-> Integer.compare(first.length(), second.length())
没有参数的形式
() -> { for (int i = 0; i < 1000; i++) doWork(); }
如果参数类型可以推断,可以不提供类型
Comparator<String> comp
= (first, second) -> Integer.compare(first.length(), second.length());
如果只有一个参数,并且可以推断,则参数的小括号可以省略
Predicate<String> pIsEmpty = s -> s.isEmpty();
参数可以添加final控制符和注解
(final String name) -> ...
(@NonNull String name) -> ...
关于闭包特性(closure)
Lambda表达式可以引用表达式作用域外的变量,但是不允许对变量进行修改:
- 基本类型的话,如int,值不能修改
- 对象类型的话,引用不能变,但是其实可以调用对象方法,对象内部数据是会变的,比较危险
- this关键字,指代表达式代码所在的类对象
通过数组对象来改变捕获对象内部状态的例子:
Integer[] value = {0};
Runnable callback = () -> {
value[0]++;
System.out.println(value[0]);
};
callback.run(); // 输出 1
函数式接口(Functional Interfaces)
- 函数式接口是只包含一个抽象方法声明的接口
- 函数式接口可以用来接收lambda表达式,当然也可以接收实现了接口的类型
- java.util.function包 定义了一些默认的函数式接口
JDK1.8 之前已有的函数式接口
- java.lang.Runnable
- java.util.concurrent.Callable
- java.security.PrivilegedAction
- java.util.Comparator
- java.io.FileFilter
- java.nio.file.PathMatcher
- java.lang.reflect.InvocationHandler
- java.beans.PropertyChangeListener
- java.awt.event.ActionListener
- javax.swing.event.ChangeListener
JDK1.8 新定义的函数式接口
java.util.function中定义了几组类型的函数式接口以及针对基本数据类型的子接口。
- Predicate 传入一个参数,返回一个bool结果, 方法为
boolean test(T t)
- Consumer 传入一个参数,无返回值,纯消费。 方法为
void accept(T t)
- Function 传入一个参数,返回一个结果,方法为
R apply(T t)
- Supplier 无参数传入,返回一个结果,方法为
T get()
- UnaryOperator 一元操作符,继承Function,传入参数的类型和返回类型相同。
- BinaryOperator 二元操作符,传入的两个参数的类型和返回类型相同,继承BiFunction
自定义函数式接口
函数接口定义好之后,就能用来保存lambda表达式。
public interface PrintVal<T> {
void printVal(T t);
}
PrintVal<String> pv = s -> System.out.println(s);
pv.printVal("yeah");
方法引用
::
操作符
可以通过::
操作符引用现有的方法来传递给函数接口,支持3中方式:
- object::instanceMethod
- Class::staticMethod
Class::instanceMethod
Arrays.sort(words, (first, second) -> first.compareTo(second)); 等价于: Arrays.sort(words, String::compareTo);
关于this和super
方法引用还支持this和super的捕获
如:this::equals
等价于 x -> this.equals(x)
super::instanceMethod 可以引用超类中的方法:
class Greeter {
public void greet() {
System.out.println("Hello, world!");
}
}
class ConcurrentGreeter extends Greeter {
public void greet() {
Thread t = new Thread(super::greet);
t.start();
}
}
构造函数引用
使用ClassName::new
来引用构造函数
class StringHolder {
String strVal;
StringHolder(String strVal) {
this.strVal = strVal;
}
}
ArrayList<String> strs = new ArrayList<>();
strs.add("hello");
strs.add("world");
List<StringHolder> shList = strs.stream().map(StringHolder::new).collect(Collectors.toList());
另外,可以使用ClassName[]::new
支持数组构造函数引用
关于接口 默认方法和静态方法
java8支持接口中定义default方法和static静态方法。
参考
- http://blog.oneapm.com/apm-tech/226.html
- http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/Lambda-QuickStart/index.html
- http://colobu.com/2014/10/28/secrets-of-java-8-functional-interface/
- http://www.importnew.com/16436.html
- http://ifeve.com/lambda/
- http://www.infoq.com/cn/articles/Java-8-Lambdas-A-Peek-Under-the-Hood
- http://blog.csdn.net/renfufei/article/details/24600507
- http://www.developer.com/java/start-using-java-lambda-expressions.html
- https://my.oschina.net/u/576554/blog/535010
- http://www.drdobbs.com/jvm/lambda-expressions-in-java-8⁄240166764