引入:通过 Runnable 接口创建线程的代码如下所示,代码中创建了一个实现 Runnable 接口的新对象, Runnable 接口只有一个方法,即 run方法,新对象实现了 run 方法,打印出一行字:真正的业务代码。实现了Runnable 接口的匿名内部类是传递了一种行为的对象,行为就是第7行的代码。此例中,程序只想传入行为,而非对象,真正的行为代码只有一行,但是调用这行业务代码却不得不多写几行样板代码,而且真正的业务代码还被隐藏在样板代码中。
@Test
public void createThread() {
new Thread(new Runnable() {
@Override
public void run() { // TODO Auto-generated method stub
System.out.println("真正的业务代码");
}
}).start();
}
如何简洁地传入一种行为呢?在Java8中,可以用 Lambda 表达式:
@Test
public void createThread() {
new Thread(() -> System.out.println("真正的业务代码")).start();
}
经过改写之后,代码量减少了,业务代码更清晰了。与利用匿名内部类的不同实现之处在于,传入了一个代码块,-> 将参数和 Lambda 表达式分开,-> 前面的参数是 run 方法的参数,而->后面的表达式是 run 方法的内容。
Lambda 表达式的形式:
无参数,一个表达式:()->System.out.println("真正的业务代码");
一个参数(可省略参数的括号),一个表达式:(a)->System.out.println(a);
多个参数,一个表达式:(a,b)->a-b;
Lambda 表达式也可以包含多行代码,此时需要把多行代码作为代码块放在花括号{}内。
区分Lambda 表达式最简单的方式是看代码中是否出现 -> 。
引用值,而非变量:
在 Lambda 表达式中,只能引用变量的值,而不能引用可变的变量,虽然变量不一定显式声明为 final 变量,但必须是 effectively final。
@Test
public void createThread() {
String name = "name";
// name = "changed name";
new Thread(() -> System.out.println(name)).start();
}
上面的代码中,若加入第4行,则编译报错:java.lang.Error: Unresolved compilation problem: Local variable name defined in an enclosing scope must be final or effectively
final,说明在 Lambda 表达式中,不能改变变量的值,即 Lambda 表达式只能引用相对 final 的值。
Lambda 表达式的类型:函数接口(@FunctionalInterface),简称 FI。
函数接口是只有一个抽象方法的接口,抽象方法是指这个函数接口本身所特有的方法,不包含从它的父类中继承过来的方法。开篇中的 Runnable 是一个函数接口,只有 run 一个抽象方法。Lambda 表达式可以实现 Runnable 接口并实现 run 方法,因此上述例子中 Lambda 表达式 参数其实就是 run 方法所需的参数。再举个例子,
匿名内部类实现,
public class LambdaTest {
JButton button = new JButton();
@Test
public void buttonClicked() {
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent event) {
// TODO Auto-generated method stub
System.out.println("按钮被点击了");
}
});
}
}
Lambda 表达式实现,
public class LambdaTest {
JButton button = new JButton();
@Test
public void buttonClicked() {
button.addActionListener(event -> System.out.println("按钮被点击了"));
}
}
对比上述的2种实现监听按钮点击事件的方法,Lambda 表达式的参数 event 是 ActionListener 接口的 actionPerformed 方法的参数,仔细观察会发现Lambda 表达式的参数 event 没有声明类型,这是因为 Javac 可以根据 Lambda 表达式的上下文来推断参数的正确类型。正如本例中 event 的参数类型,是根据 ActionListener 接的 actionPerformed 方法的参数类型进行推断的。
在 Java8 中,函数接口以 @FunctionalInterface 类注解,重要且常见的函数接口有:
总结:Lambda 表达式是一个将行为作为数据传递的匿名方法;
函数接口是仅有一个抽象方法的接口,是 Lambda 表达式的类型。