Lambda表达式
Java lambda表达式是Java 8中新添加的.Java lambda表达式是Java进入函数式编程的第一步。 lambda表达式可以创建不属于一个类的函数。lambda表达式可以像对象一样被传递,并按需执行。
Lambda表达式和单一方法接口
函数式编程通常用于实现事件监听器。Java中的事件监听器通常定义为含有单一方法的Java接口。下面是一个虚拟的单一方法接口的例子:
public interface StateChangeListener {
public void onStateChange(State oldState, State newState);
}
这个Java接口定义了一个单一的方法,只要状态改变(在被检测到的情况下)就被调用。
在Java 7中,你必须实现这个接口来监听状态变化。想象一下,你有一个名为StateOwner的类,它可以注册状态事件监听器。下面是一个例子:
public class StateOwner {
public void addStateListener(StateChangeListener listener) { ... }
}
在Java 7中,你可以使用匿名接口实现来添加事件侦听器,如下所示:
StateOwner stateOwner = new StateOwner();
stateOwner.addStateListener(new StateChangeListener() {
public void onStateChange(State oldState, State newState) {
// 实现代码
}
});
首先创建一个StateOwner实例。然后,将StateChangeListener接口的匿名实现作为侦听器添加到StateOwner实例中。
在Java 8中,你可以使用Java lambda表达式添加事件侦听器,如下所示:
StateOwner stateOwner = new StateOwner();
stateOwner.addStateListener(
(oldState, newState) -> System.out.println("State changed")
);
lambda表达式是这部分:
(oldState, newState) -> System.out.println("State changed")
lambda表达式与addStateListener()方法参数的参数类型相匹配。如果lambda表达式匹配参数类型(在本例中是StateChangeListener接口),那么lambda表达式就变成一个实现与该参数相同接口的函数。
Java lambda表达式只能用于与其匹配的类型是单个方法接口的情况。在上面的例子中,lambda表达式被用作参数,其中参数类型是StateChangeListener接口。这个接口只有一个方法。因此,lambda表达式与该接口成功匹配。
Lambda表达式与接口相匹配
单个方法接口有时也被称为功能接口。将Java lambda表达式与功能接口匹配分为以下步骤:
- 接口是否只有一个方法?
- lambda表达式的参数是否与接口唯一方法的参数相匹配?
- lambda表达式的返回值类型是否与接口唯一方法的返回值类型一致?
如果这三个问题的答案是肯定的,那么给定的lambda表达式与接口成功匹配。
Lambda类型推断
在Java8之前,当你在实现匿名接口时必须指定要实现的接口。下面是文章开头的匿名接口实现示例:
stateOwner.addStateListener(new StateChangeListener() {
public void onStateChange(State oldState, State newState) {
// 代码实现
}
});
对于lambda表达式,类型通常可以从周围的代码中推断出来。例如,参数的接口类型可以从addStateListener()方法的方法声明(StateChangeListener接口上的单个方法)推断出来。这称为类型推断。编译器通过在别处查找类型来推断参数的类型 - 在这个例子中是通过方法定义来推断的。下面是本文开头的示例,显示在lambda表达式中未提及StateChangeListener接口:
stateOwner.addStateListener(
(oldState, newState) -> System.out.println("State changed")
);
在lambda表达式中,通常也可以推断参数类型。在上面的例子中,编译器可以从onStateChange()方法声明中推断出它们的类型。因此,参数oldState和newState的类型是从onStateChange()方法的方法声明中推断出来的。
lambda参数
由于Java lambda表达式实际上只是方法,因此lambda表达式可以像方法一样使用参数。前面显示的lambda表达式的(oldState,newState)部分指定了lambda表达式所需的参数。这些参数必须与单个方法接口上方法的参数相匹配。在这个例子中,这些参数必须与StateChangeListener接口的onStateChange()方法的参数匹配:
public void onStateChange(State oldState, State newState);
至少需要保证lambda表达式和方法中的参数数目必须匹配。
其次,如果你在lambda表达式中指定了任何参数类型,则这些类型也必须匹配。
零个参数
如果lambda表达式匹配的方法不带任何参数,那么你可以像这样写lambda表达式:
() -> System.out.println("Zero parameter lambda");
注意括号中间没有内容。这是表示lambda不需要参数。
一个参数
如果Java lambda表达式匹配的方法需要一个参数,则可以像这样编写lambda表达式:
(param) -> System.out.println("One parameter: " + param);
注意参数在括号内列出。
当lambda表达式采用单个参数时,也可以省略括号,如下所示:
param -> System.out.println("One parameter: " + param);
多参数
如果lambda表达式匹配的方法需要多个参数,则参数需要在括号内列出。示例如下:
(p1, p2) -> System.out.println("Multiple parameters: " + p1 + ", " + p2);
只有当方法采用单个参数时,可以省略括号。
参数类型
如果编译器无法从lambda匹配的函数接口方法中推断出参数类型,那么为lambda表达式指定参数类型有时可能是必须的。别担心,编译器会告诉你什么时候是这样。这是一个Java lambda参数类型的例子:
(Car car) -> System.out.println("The car is: " + car.getName());
正如你所看到的,就像你在其他地方的方法中声明一个参数时,或者当做一个接口的匿名实现一样,car参数的类型(Car)被写在参数名称本身的前面。
lambda函数主体
lambda表达式的主体,以及它所表示的函数/方法的主体,在lambda声明中的 - >的右侧被指定:下面是一个例子:
(oldState, newState) -> System.out.println("State changed")
如果你的lambda表达式需要由多行组成,那么你可以将lambda函数体包含在{}括号中,Java也需要在其他地方声明方法。下面是一个例子:
(oldState, newState) -> {
System.out.println("Old state: " + oldState);
System.out.println("New state: " + newState);
}
Lambda表达式返回值
你可以从Java lambda表达式中返回值,就像你可以从方法中返回一样。你只需要将一个return语句添加到lambda函数体中,就像这样:
(param) -> {
System.out.println("param: " + param);
return "return value";
}
如果你的lambda表达式只是计算一个返回值并返回它,你可以用更短的方式指定返回值。不需要像下面这样:
(a1, a2) -> { return a1 > a2; }
而是这样:
(a1, a2) -> a1 > a2;
编译器会发现表达式a1> a2是lambda表达式的返回值
Lambda作为一个对象
Java lambda表达式本质上是一个对象。你可以将一个lambda表达式分配给一个变量并将其传递。下面里是一个例子:
public interface MyComparator {
public boolean compare(int a1, int a2);
}
MyComparator myComparator = (a1, a2) -> return a1 > a2;
boolean result = myComparator.compare(2, 5);
第一个代码块显示了lambda表达式实现的接口。第二个代码块显示了lambda表达式的定义,lambda表达式如何分配给变量,最后是如何通过调用它实现的接口方法调用lambda表达式。
下一篇:练习