Java 语言在 JDK8 中引入了 lambda 表达式,它是一个可传递的代码块,可以在以后执行一次或多次,我们来看 一个小例子。
String[] strings = {"a", "A", "b", "B"};
Arrays.sort(strings);
System.out.println(Arrays.toString(strings));
// output
// [A, B, a, b]
String
类实现了 Comparable
接口,所以可以当我们使用 Arrays.sort
进行排序时会使用字典排序,若我们想要使用其他排序方法,此时可以使用Comparator
比较器。
Arrays.sort(strings, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o1.toLowerCase().compareTo(o2.toLowerCase());
}
});
System.out.println(Arrays.toString(strings));
// output
// [A, a, B, b]
我们可以看到上述代码中将一个代码块传递到了 sort 方法中,并且这个代码块在未来某个时间会被调用。在之前,在 Java 中传递一个代码段并不容易,因为 Java 是一种面向对象的语言,所以必须构造一个对象,这个对象的类需要有一个方法能保护所需的代码。
lambda 表达式的语法
但 Java 引入 lambda 表达式之后,我们可以将代码改写成如下:
Arrays.sort(strings, (String o1, String o2) -> o1.toLowerCase().compareTo(o2.toLowerCase()));
System.out.println(Arrays.toString(strings));
如果无法用一个表达式写完,可以用 {}
(String first, String second) -> {
if (first.length < second.length) {
return -1;
} else if (first.length < second.length) {
return 1;
} else {
return 0;
}
}
如果没有参数,也需要写 ()
() -> Systen.out.println("hello");
如果可以推到出参数类型,则可以忽略其类型,并且无需指定 lambda 的返回类型,可以通过上下文推到得出。
(o1, o2) -> o1.length - o2.length;
函数式接口
对于只有一个抽象方法的接口,需要这种接口时,就可以提供一个 lambda 表达式,这种接口称为函数式接口(functional interface),如上例中的 Comparator(重新声明了 Object 中的 equals 方法, 该方法不是抽象的,因此只有一个 compare 抽象方法), Comparator 是一个函数式接口,当我们提供一个 lambda 表达式时,在底层, Arrays.sort 方法会接收实现了 Comparator<String>
的某个类的对象,在这个对象上调用 compare 方法会执行这个 lambda 表达式的代码,这些对象的管理完全取决于具体实现,与使用传统的内联类相比,可能高效许多 。
想要用 lambda 表达式做某些处理,还是要谨记表达式的用途,为它建立一个特定的函数式接口。java.util.function
包中有一个尤其有用的接口 Predicate
public interface Predicate<T> {
/**
* Evaluates this predicate on the given argument.
*
* @param t the input argument
* @return {@code true} if the input argument matches the predicate,
* otherwise {@code false}
*/
boolean test(T t);
// other code ...
}
Collection
类有一个方法 removeIf
, 参数就是 Predicate
list.removeIf((e) -> e == null); // 删除list中的 null 元素
方法引用
有时可能已经有现成的方法可以完成想要传递到其他代码中的动作,此时可以使用方法引用(method reference),如下例
x -> System.out.println(x);
// method reference
System.out::println
我们可以利用方法引用改进上面 lambda 表达式的代码
list.removeIf((e) -> e == null);
// reference method
list.removeIf(Objects::isNull);
Arrays.sort(strings, (String o1, String o2) -> o1.toLowerCase().compareTo(o2.toLowerCase()));
// method reference
Arrays.sort(strings, Comparator.comparing(String::toLowerCase));
我们可以看到方法引用的语法主要有以下三种:
- object::instanceMethod
- Class::staticMethod
- Class::instanceMethod
前两种情况等价于提供方法参数的 lambda 表达式:
System.out.println(x)
x -> System.out::println
(x, y) -> Math.pow(x, y)
Math::pow
第三种情况,第一个参数会成为方法的目标,如
(x, y) -> x.compareToIgnoreCase(y);
String::compareToIgnoreCase
注意:
- 方法引用支持 this, 如
this::equals
等同于x -> this.equals(x)
- 使用 super 也和合法
super::instanceMethod
变量作用域
通常情况下,我们可能希望能够在 lambda 表达式中访问外围方法或者类中的变量,观察一下代码:
public static void main(String[] args) {
String text = "hello";
ActionListener listener = event -> {
System.out.println(text);
};
new Timer(1000, listener).start();
}
// output
//hello
//...
观察上述代码以及输出结果可知:lambda 表达式可以访问其外围作用域的变量 text
, 这里会存在一个问题,lambda 表达式的代码可能会在主方法调用返回很久之后才会运行,而那时这个参数已经不存在了,该如何保存这个参数呢?
lambda 表达式由三部分组成: 一个代码块,参数,自由变量的值。
在上述代码中,lambda 表达式有一个自由变量 text,表示lambda 表达式的数据结构必须存储自由变量的值,称为被 lambda 表达式捕获(captured)具体的实现细节为:可以把 lambda 表达式转化为包含一个方法的对象,这样自由变量的值就会复制到这个对象的实例变量中。
上述讨论的自由变量与代码块有一个术语L:闭包(closure), lambda 表达式就是 Java中的闭包。我们需要注意 lambda 只能引用值不会改变的变量,即该变量必须是实际上的最终变量(effectively final):即这个自由变量在外围作用域不能被改变,在 lambda 表达式中也不能被改变,这主要是出于并发安全性考虑。
Reference
1.Java核心技术卷一