语法

第一个 lambda 表达式:参数、箭头以及一个表达式。

1
(String first, String second) -> first.length() - second.length()

如果代码要完成的计算无法放在一个表达式中,可以把代码放在 {} 中,并包含显示的 return 语句。

1
2
3
4
5
(String first, String second) -> {
    if (first.length() < second.length()) return -1;
    else if (first.length() > second.length()) return 1;
    else return 0;
}

即使 lambda 表达式没有参数,仍要提供空括号,就像无参方法一样。

1
() -> {for (int i = 100; i >=0; i--) System.out.println(i);} 

如果可以推导出一个 lambda 表达式的参数类型,则可以忽略其类型。

1
2
Comparator<String> comp = (first, second) // Same as (String fist, String second)
			     -> first.length() - second.length();

如果方法只有一个参数,且这个参数的类型可以推导而出,那么甚至可以省略小括号。

1
2
3
ActionListener listener = event -> 
    System.out.println("The time is " + new Date());
	// Instead of (event) -> ...or (ActionEvent event) -> ...

无需指定指定 lambda 表达式的返回类型,表达式返回类型由上下文推导而出。

1
(String first, String second) -> first.length() - second.length();

函数式接口

对于只有一个抽象方法的接口,当需要这种接口的对象时,可以提供一个 lambda 表达式。这种接口称为函数式接口。

方法引用

方法引用有三种常见方式,:: 操作符分隔方法名与对象或类名。

  • object::instanceMethod
  • Class::staticMethod
  • Class::instanceMethod

前两种情况等价于提供方法参数的 lambda 表达式。例如 System.out::println 等价于 x -> System.out.println(x)Math.pow 等价于 (x, y) -> Math.pow(x, y)

对于第三种情况,第一个参数会成为方法的目标。例如 String::compareToIgnoreCase 等同于 (x, y) -> x.compareToIgnoreCase(y)

同时可以在方法引用中使用 this 和 super 参数:

  • this::method 等价于 x -> this.method(x)
  • super::instanceMethod

构造器引用

Person::new 是 Person 构造器的一个引用。具体引用的是哪一个构造器则由具体的上下文决定。

1
2
3
ArrayList<String> names = ...;
Stream<Person> stream = names.stream().map(Person::new);
List<Person> people = stream.collect(Collectors.toList());

map 方法会为各个列表元素调用 Person(String) 构造器。如果有多个 Person 构造器,编译器会选择有一个 String 参数的构造器,因为它从上下文推导出这是在对一个字符串调用构造器。

假设我们需要一个 Person 对象数组。Stream 接口有一个 toArray 方法可以返回 Object 数组,但是我们需要的是是一个精确类型的 Person 引用数组,stream 库利用构造器引用解决了这个问题。

1
Object[] people = stream.toArray();
1
Person[] people = stream.toArray(Person::new);

变量作用域与闭包

通常,我们在 lambda 表达式中可能需要访问外围方法或类中的变量:

1
2
3
4
5
6
7
public static void repeatedMsg(String text, int delay) {
   ActionListener listener = event -> {
      System.out.println(text); // lambda 表达式中访问外围方法中的变量 text
      Toolkit.getDefaultToolkit().beep();
   };
   new Timer(delay, listener).start();
}

为了更好地解释,我们重新认识 lambda 表达式。lambda 表达式有三个部分:

  1. 一个代码块
  2. 参数
  3. 自由变量的值,这是指非参数而且不在 lambda 表达式中定义的变量

在上面的例子中,lambda 表达式有一个自由变量 text,我们说它被 lambda 表达式捕获(captured)。 lambda 表达式的数据结构必须存储自由变量的值,因为 lambda 表达式的代码可能会在 repeatedMsg 调用很久之后才会执行,而那时这个参数的变量已经不存在了。如何保存?可以把 lambda 表达式转换为包含一个方法的对象,这样自由变量会复制到这个对象的实例变量中。

lambda 表达式中的捕获的局部变量必须是最终变量(effective final)。所谓最终变量是指这个变量初始化之后不会再为它赋新值,即不可修改。

但 lambda 表达式对外部类的成员变量(类变量和实例变量)可读可写。