JAVA lambda表达式
前言
对于java语言,如果要定制一个类的比较器,则需要定义compare方法,如按照字符串的长度进行排序:
class LengthComparator implements Comparator<Strring>
{
public int compare(String first, String second)
{
return first.length() - second.length();
}
}
...
Arrays.sort(strings, new LenthComparator);
在数组完成排序之前,sort方法会一直调用compare方法,即将compare代码块传递给sort对象。
lambda表达式
如果我们将上面的compare代码块写成lambda表达式的形式:
(String first, String second)
-> first.length() - second.length()
编译器在type check时,length()方法表明first和second是字符串类型,因此可以忽略声明其类型:
(first, second)
-> first.length() - second.length()
如果一行的表达式太难为复杂的逻辑,lambda表达式也支持代码块的写法:
(String first, String second) ->
{
if (first.length() < second.length()) return -1;
else if (first.length() > second.length()) return 1;
else return 0;
}
如果lambda表达式中不传入参数,仍需要保留空括号,像无参数方法一样:
() -> 0
注意:lambda表达式需要在每个branch都返回值(函数式编程的规则)。
函数式接口
函数式接口(functional interface)是指只有一个抽象方法的接口,需要这种接口的对象时,可以提供一个lambda表达式。如上面提到的Comparator就是只有一个方法的接口,可以提供一个lambda表达式:
Arrays.sort(strings,
(first, second) -> first.length() - second.length());
在底层,Arrays.sort方法接收实现了Comparator<String>的某个类的对象,而在这个对象上调用compare方法会执行传入的lambda表达式。通过这层关系,lambda表达式可以转换为接口。
方法引用
有时可能有现成的方法来完成你所期望的lambda表达式效果,例如,对字符串数组进行忽略大小写的排序:
Arrays.sort(strings, String::compareToIgnoreCase);
String::compareToIgnoreCase是一个方法引用,用::操作符分隔对象(类名)和方法名。主要有三种情况:
- object::instanceMethod
- Class::staticMethod
- Class::instanceMethod
方法引用中使用this参数和super参数也是合法的。
对于前两种情况,方法引用相当于提供方法参数的lambda表达式,如Math::pow等价于(x,y) -> Math.pow(x,y);对于第三种情况,第一个参数成为方法的对象,如String::compareToIgnoreCase等价于(x,y) -> x.compareToIgnoreCase(y)。
变量作用域
lambda表达式可以访问外围方法或类中的变量:
public static void repeatMessage(String text, int delay)
{
ActionListener listener = event ->
{
System.out.println(text);
Toolkit.getDefaultTookit().beep();
};
new Timer(delay, listener).start();
}
调用repeatMessage并返回后,lambda表达式才开始调用,但text的值已经被lambda表达式捕获,储存在其数据结构中。因此,可以说在java中lambda表达式就是闭包(closure)。
java对捕获值进行了限制,只能引用值不会改变的变量,在lambda表达式中修改捕获变量的值或引用可能在外部改变的变量,均是不合法的。
由于lambda表达式与嵌套块有相同的作用域,在lambda表达式中声明一个与局部变量同名的参数是不合法的:
int first = 0;
Comparator<String> comp =
(first, second) -> first.length() - second.length());
// Error: Variable first already defined
深入Comparator
Comparator接口包含很多静态方法来创建比较器,如comparing方法可以传入一个“键提取器”函数(将类型T映射为一个可比较的类型),如按照名字对一个Person对象数组people进行排序:
Arrays.sort(people, Comparator.comparing(Person::getName));
另外可以把比较器与thenComparing方法连接起来,如先比较姓,姓相同则比较名:
Arrays.sort(people,
Comparator.comparing(Person::getLastName)
.thenComparing(Persin::getFirstName));
进一步,也可以为comparing和thenComparing方法指定一个比较器,如按照人名长度进行排序:
Arrays.sort(people, Comparator.comparing(Person::getName,
(first, second) -> first.length() - second.length()));
Ending & Reference
感谢阅读,本文内容整理自:
[1] Cay S. Horstmann. Java核心技术·卷 I[M]. 第10版. 北京: 机械工业出版社, 2016: 231-242.