lambda表达式是Java8带给我们的几个重量级特性之一,借用lambda表达式,可以让我们的Java程序设计更加简洁和高效。要深入理解lambda表达式,需要理解函数式接口,lambda表达式的表示形式,以及方法引用。
1. 函数式接口
函数式接口是只有一个抽象方法的接口,用作lambda表达式的类型。
函数式接口可以用@FunctionalInterface注解,可以把它放在注解的前面,但它是非必须的,使用注解只是为了方便编译器作语法检查。只要接口只包含一个抽象方法,虚拟机会自动判断。在接口中添加了 @FunctionalInterface 的注解后,该接口就只允许有一个抽象方法,否则编译器也会报错。如下,java.lang.Runnable 就是一个函数式接口:
| 1 | 
 | 
函数式接口的重要属性是:我们能够使用 lambda 实例化它们,lambda 表达式让你能够将函数作为方法参数,或者将代码作为数据对待。
2. lambda表达式
lambda表达式是一种紧凑的传递行为的方式。lambda表达式由三部分组成:第一部分为一个括号内用逗号分隔的形式参数,参数是函数式接口里面方法的参数;第二部分为一个箭头符号:->;第三部分为方法体,可以是表达式和代码块。语法如下:
- 方法体为表达式,该表达式的值作为返回值返回 - 1 - (parameters) -> expression - 例如: - 1 - Supplier<String> i = ()-> "Supplier test"; - 这里,”Supplier test”就是lambda表达式i的get方法返回值。 
- 方法体为代码块,必须用 {} 来包裹起来,且需要一个 return 返回值,但若函数式接口里面方法返回值是 void,则无需返回值。 - 1 - (parameters) -> { statements; } - 例如上面的例子的等价形式为: - 1 - Supplier<String> i = ()-> {return "Supplier test";}; - 下面是匿名内部类的代码: - 1 
 2
 3
 4
 5
 6- button.addActionListener(new ActionListener() { 
 
 public void actionPerformed(ActionEvent e) {
 System.out.print("Hello lambda in actionPerformed");
 }
 });- 下面是使用lambda表达式后的代码: - 1 
 2
 3
 4- button.addActionListener( 
 //actionPerformed 有一个参数 e 传入,所以用 (ActionEvent e)
 (ActionEvent e)-> System.out.print("Hello lambda in actionPerformed")
 );- 其实,lambda表达式可以自己根据上下文推断参数类型,无需显示指定: - 1 - button.addActionListener( e -> System.out.print("Hello lambda in actionPerformed")); 
- lambda表达式的几种变体 
 将lambda表达式赋值给一个一个变量,lambda表达式没有参数:- 1 - Runnable noArguments = () -> System.out.println("Hello World"); //(1) - 将lambda表达式赋值给一个一个变量,lambda表达式的参数类型由编译器推导出来: - 1 - ActionListener oneArgument = event -> System.out.println("button clicked"); //(2) - 将lambda表达式赋值给一个一个变量,lambda表达式有多个参数类型,参数类型由编译器推导出来: - 1 - BinaryOperator<Long> add = (x, y) -> x + y; //(3) - 将lambda表达式赋值给一个一个变量,lambda表达式有多个参数类型,显示声明lambda表达式类型: - 1 - BinaryOperator<Long> addExplicit = (Long x, Long y) -> x + y; //(4) - 如上所示,有lambda表达式中的参数类型都是由编译器推断得出的。这当然不错,但有时最好也可以显式声明参数类型,此时就需要使用小括号将参数括起来,多个参数的情况也是如此,如(4)。 - 注意: - 目标类型是指lambda表达式所在上下文环境的类型。比如,将lambda表达式赋值给一个局部变量,或传递给一个方法作为参数,局部变量或方法参数的类型就是lambda表达式的目标类型。lambda表达式的目标类型类型依赖于上下文环境,是由编译器推断出来的。如(1)和(2)的lambda表达式的目标类型分别是Runnable和ActionListener类型,可以把lambda表达式看作实现了该接口的内部类的实例,noArguments和oneArgument分表表示指向实例的引用。 
- lambda表达式能引用表达式之外定义的既成事实的final变量。虽然无需将变量声明为final,但在lambda表达式中,也无法用作非终态变量。下面的可以编译通过: - 1 
 2- String name = getUserName(); 
 button.addActionListener(event -> System.out.println("hi" + name));- 但下面不能编译通过: - 1 
 2
 3- String name = getUserName(); 
 name = formatUserName(name);
 button.addActionListener(event -> System.out.println("hi" + name));- 也就是说,lambda只能引用表达式之外不会改变的变量,之所以有这样的限制,是因为若变量可以改变,并发执行多个lambda表达式时就会不安全。 
3. 方法引用
可以用已经定义好的类中的方法来表示一个lambda表达式。例如:1
button.addActionListener(event -> System.out.println(event));
可以使用System.out::println的方法引用表示成如下形式:1
button.addActionListener(System.out::println);
总的来说,方法引用有如下四种使用情况:
- 对象::实例方法
- 类::静态方法
- 类::实例方法
- 类::new
在前两种情况,方法引用等同于提供方法参数的lambda表达式。如System.out::println等同于x -> System.out.println(x)。类似的,Math::pow等同于(x, y) -> Math.pow(x, y)。
第三种情况,第一个参数为执行方法的对象,即当lambda表达式的的第一个参数是要执行的方法体所属的对象时,可以使用类::实例方法的方法引用代替。例如:String::compareToIgnoreCase等同于(x, y) -> x.compareToIgnoreCase(y),这里x就是compareToIgnoreCase方法所属对象。其实这也是合理的,因为Java中实例方法拥有者为一个具体的对象,只有一个对象才能调用实例方法。
下面是一个具体的引用类::实例方法的例子,其中personList.stream().forEach(x -> x.getName())和personList.stream().forEach(Person::getName)是等价的。
| 1 | import java.util.ArrayList; | 
第四种情况则是构造器的引用,对于拥有多个构造器的类,选择使用哪个引用取决于上下文。需要注意的是,虽然方法引用使用的是一个方法,但不需要在后面加括号,因为这里并不调用该方法。使用该方法只是提供了一种和lambda表达式等价的一种结构,在需要时才会调用。凡是使用lambda表达式的地方,就可以使用方法引用。
方法引用还可以使用this或super参数,表示引用本类或父类中的方法,例如:this::equals就等同于x -> this.equals(x)。
| 1 | public class ConcurrentGreeter extends Greeter { | 
该例子中,相当于引用父类的方法,实现了一个这样的lambda表达式,返回类型为Runnable:1
Thread t = new Thread(() -> System.out.println("Hello, world"));
4. 什么时候使用Lambda表达式
函数式接口是lambda表达式的类型,因此,若函数的形参传递的是一个函数式接口类型的引用,则可以直接给该形参传递lambda表达式。当然,也可以给该形参传递实现该接口的匿名类对象或实例。下面的三种方法是等效的,当然,使用lambda表达式是最简洁的。
| 1 | import java.util.Arrays; | 
参考: