本文将介绍 Java 8 新增的 Lambda 表达式,包括 Lambda 表达式的常见用法以及方法引用的用法,并对 Lambda 表达式的原理进行分析,最后对 Lambda 表达式的优缺点进行一个总结。

image

1. 概述

Java 8 引入的 Lambda 表达式的主要作用就是简化部分匿名内部类的写法。

能够使用 Lambda 表达式的一个重要依据是必须有相应的函数接口。所谓函数接口,是指内部有且仅有一个抽象方法的接口。

Lambda 表达式的另一个依据是类型推断机制。在上下文信息足够的情况下,编译器可以推断出参数表的类型,而不需要显式指名。

2. 常见用法

2.1 无参函数的简写

无参函数就是没有参数的函数,例如 Runnable 接口的 run() 方法,其定义如下:

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

在 Java 7 及之前版本,我们一般可以这样使用:

new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello");
        System.out.println("Jimmy");
    }
}).start();

从 Java 8 开始,无参函数的匿名内部类可以简写成如下方式:

() -> {
    执行语句
}

这样接口名和函数名就可以省掉了。那么,上面的示例可以简写成:

new Thread(() -> {
    System.out.println("Hello");
    System.out.println("Jimmy");
}).start();

当只有一条语句时,我们还可以对代码块进行简写,格式如下:

() -> 表达式

注意这里使用的是表达式,并不是语句,也就是说不需要在末尾加分号。

那么,当上面的例子中执行的语句只有一条时,可以简写成这样:

new Thread(() -> System.out.println("Hello")).start();

2.2 单参函数的简写

单参函数是指只有一个参数的函数。例如 View 内部的接口 OnClickListener 的方法 onClick(View v),其定义如下:

public interface OnClickListener {
    /**
     * Called when a view has been clicked.
     *
     * @param v The view that was clicked.
     */
    void onClick(View v);
}

在 Java 7 及之前的版本,我们通常可能会这么使用:

view.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        v.setVisibility(View.GONE);
    }
});

从 Java 8 开始,单参函数的匿名内部类可以简写成如下方式:

([类名 ]变量名) -> {
    执行语句
}

其中类名是可以省略的,因为 Lambda 表达式可以自己推断出来。那么上面的例子可以简写成如下两种方式:

view.setOnClickListener((View v) -> {
    v.setVisibility(View.GONE);
});
view.setOnClickListener((v) -> {
    v.setVisibility(View.GONE);
});

单参函数甚至可以把括号去掉,官方也更建议使用这种方式:

变量名 -> {
    执行语句
}

那么,上面的示例可以简写成:

view.setOnClickListener(v -> {
    v.setVisibility(View.GONE);
});

当只有一条语句时,依然可以对代码块进行简写,格式如下:

([类名 ]变量名) -> 表达式

类名和括号依然可以省略,如下:

变量名 -> 表达式

那么,上面的示例可以进一步简写成:

view.setOnClickListener(v -> v.setVisibility(View.GONE));

2.3 多参函数的简写

多参函数是指具有两个及以上参数的函数。例如,Comparator 接口的 compare(T o1, T o2) 方法就具有两个参数,其定义如下:

@FunctionalInterface
public interface Comparator<T> {
    int compare(T o1, T o2);
}

在 Java 7 及之前的版本,当我们对一个集合进行排序时,通常可以这么写:

List<Integer> list = Arrays.asList(1, 2, 3);
Collections.sort(list, new Comparator<Integer>() {
    @Override
    public int compare(Integer o1, Integer o2) {
        return o1.compareTo(o2);
    }
});

从 Java 8 开始,多参函数的匿名内部类可以简写成如下方式:

([类名1 ]变量名1, [类名2 ]变量名2[, ...]) -> {
    执行语句
}

同样类名可以省略,那么上面的例子可以简写成:

Collections.sort(list, (Integer o1, Integer o2) -> {
    return o1.compareTo(o2);
});
Collections.sort(list, (o1, o2) -> {
    return o1.compareTo(o2);
});

当只有一条语句时,依然可以对代码块进行简写,格式如下:

([类名1 ]变量名1, [类名2 ]变量名2[, ...]) -> 表达式

此时类名也是可以省略的,但括号不能省略。如果这条语句需要返回值,那么 return 关键字是不需要写的。

因此,上面的示例可以进一步简写成:

Collections.sort(list, (o1, o2) -> o1.compareTo(o2));

最后呢,这个示例还可以简写成这样:

Collections.sort(list, Integer::compareTo);

咦,这是什么特性?这就是我们下面要讲的内容:方法引用。

3. 方法引用

方法引用也是一个语法糖,可以用来简化开发。

在我们使用 Lambda 表达式的时候,如果“->”的右边要执行的表达式只是调用一个类已有的方法,那么就可以用「方法引用」来替代 Lambda 表达式。

方法引用可以分为 4 类:

  • 引用静态方法;
  • 引用对象的方法;
  • 引用类的方法;
  • 引用构造方法。

下面按照这 4 类分别进行阐述。

3.1 引用静态方法

当我们要执行的表达式是调用某个类的静态方法,并且这个静态方法的参数列表和接口里抽象函数的参数列表一一对应时,我们可以采用引用静态方法的格式。

假如 Lambda 表达式符合如下格式:

([变量1, 变量2, ...]) -> 类名.静态方法名([变量1, 变量2, ...])

我们可以简写成如下格式:

类名::静态方法名

注意这里静态方法名后面不需要加括号,也不用加参数,因为编译器都可以推断出来。下面我们继续使用 2.3 节的示例来进行说明。

首先创建一个工具类,代码如下:

public class Utils {
    public static int compare(Integer o1, Integer