新发布的 Java 8 包含众多新特性,其中之一是 Java 8 支持了被 .net 玩烂了的 Lambda 表达式。
当然我们也不能说 Java 是后知后觉,Java 有自身的考虑。我觉得可能性更大的是 Java 支持 Lambda,只是采用了这种语法(或者说这种编程方式),实现上仍然是以前那套东西,只是在编译阶段和运行阶段做了一些“手脚”,让大家吃上了 Lambda 这颗语法糖。
基本使用
先看看 Lambda 表达式最简单的使用:
public class Main { public static void main(String[] args) { new Thread(() -> System.out.println("baidu.com")).start(); } } 复制代码
经常用 Thread() 写 Demo 的同学,肯定能一眼发现这当中的 Lambda 表达式其实就是对应了 Runnable 中的 run() 方法。但是第一次接触 Lambda 表达式的同学,可能很难理解,这怎么就能对应上 Runnable 中的 run() 方法了?其实这里能用 Lambda 表达式是因为 Runnable 接口属于函数式接口。
所谓的函数式接口是指该接口只有唯一一个需要被实现的抽象接口。
这里的 Thread(Runnable target) 所需要传入的 Runnable 对象,正是一个只有一个方法需要实现的接口,属于函数式接口。而且函数式接口还有一个显式的特征,就是会带一个 @FunctionalInterface 注解:
@FunctionalInterface public interface Runnable { public abstract void run(); } 复制代码
除了无参、单行代码的情况,Lambda 表达式支持以下几种写法:
- 执行语句块
public class Main { public static void main(String[] args) { new Thread(() -> { System.out.println("baidu.com"); System.out.println("baidu.com"); }).start(); } } - 接受显式参数
public class Main { public static void main(String[] args) { List list = new LinkedList<>(); // To prepare data ... Collections.sort(list, (Integer a, Integer b) -> { System.out.println(a); System.out.println(b); return Integer.compare(a, b); }); } } - 接受隐式参数
public class Main { public static void main(String[] args) { List list = new LinkedList<>(); // To prepare data ... Collections.sort(list, (a, b) -> { System.out.println(a); System.out.println(b); return Integer.compare(a, b); }); } } - 自动处理返回值
public class Main { public static void main(String[] args) { List list = new LinkedList<>(); // To prepare data ... Collections.sort(list, (a, b) -> Integer.compare(a, b)); } } - 方法引用
public class Main { public static void main(String[] args) { List list = new LinkedList<>(); // To prepare data ... Collections.sort(list, Integer::compare); } }
也就是说,最简单的 Lambda 表达式的形式化格式为:parameters -> an expression ,当有多个语句要执行时,可以使用 parameters -> {expressions;};。同时 Lambda 表达式具有推导参数类型和自动处理返回值的能力。
Lambda 的实现细节
在 Java 8 之前,我们主要有三种方式达到类似 Lambda 表达式的效果
- 定义类来实现接口:
public class Main { public static void main(String[] args) { Runnable runnable = new RunnableOuterImpl(); new Thread(runnable).start(); } } - 使用内部类:
public class Main { static final class RunnableImpl implements Runnable { @Override public void run() { System.out.println("baidu.com"); } }
public static void main(String[] args) { Runnable runnable = new RunnableImpl(); new Thread(runnable).start(); } 复制代码
- }
- 使用匿名内部类:
public class Main { public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { System.out.println("baidu.com"); } }).start(); } }
那么 Lambda 是通过以上三种方式的何种来实现的呢?
事实上,Lambda 表达式是通过内部类来实现的。完整的 Lambda 表达式的流程如下:
- 在编译期,先创建一个私有的静态方法,静态方法的实现就是我们 Lambda 表达式里要做的事情
这一点,我们可以通过编译源码,再重新编译回去证实。我们可以编译以上的 Lambda 版本的 java 文件,得到对应的 class 文件,再通过 javap 进行反编译。得到以下反编译内容:
Compiled from "Main.java" public class com.baidu.Main { public com.baidu.Main(); public static void main(java.lang.String[]); private static void lambda$main$0(); } 复制代码
可以看到在 Main 类里面多了一个 lambdamainmainmain0 方法,说明 Lambda 表达式首先先生成一个私有的静态函数。这个函数里的实现就是我们在 Lambda 表达式里面的执行内容。
- 在运行时,Lambda 表达式首次被执行时,动态生成一个对应接口的实现类
由于是运行时,最好的验证方法是通过断点的形式。在执行 Lambda 表达式的位置断点,可以看到执行表达式的时,是调用了 java.lang.invoke.LambdaMetafactory 类里面的 metafactory() 方法
public static CallSite metafactory(MethodHandles.Lookup caller, String invokedName, MethodType invokedType, MethodType samMethodType, MethodHandle implMethod, MethodType instantiatedMethodType) throws LambdaConversionException { AbstractValidatingLambdaMetafactory mf; mf = new InnerClassLambdaMetafactory(caller, invokedType, invokedName, samMethodType, implMethod, instantiatedMethodType, false, EMPTY_CLASS_ARRAY, EMPTY_MT_ARRAY); mf.validateMetafactoryArgs(); return mf.buildCallSite(); } 复制代码
为了确定这个动态生成的类在运行时真实存在,我们可以执行 Main.java 的时候,指定 JVM 参数 -Djdk.internal.lambda.dumpProxyClasses 类导出 Lambda 表达式生成的动态代理类。之后我们会在 Main.class 的同级目录下看到一个新的 class 文件:Main$$Lambda$1.class。
然后通过通过 javap 反编译一下:
➜ WorkSpace javap -c -p com.baidu.Main\$\$Lambda\$1 final class com.baidu.Main$$Lambda$1 implements java.lang.Runnable { private com.baidu.Main$$Lambda$1(); Code: 0: aload_0 1: invokespecial #10 // Method java/lang/Object."<init>":()V 4: return public void run(); Code: 0: invokestatic #17 // Method com/baidu/Main.lambda$main$0:()V 3: return } 复制代码
可以看到运行时,JVM 会为 Lambda 表达式创建一个 Runnable 接口的实现类。里面有一个默认的构造方法和必须实现的 run() 方法。在 run() 方法中,调用了一个静态方法,这个静态方法就是我们刚刚说的,在编译期,生成的那个私有静态方法 lambdamainmainmain0()。
总结
总结一下,Lambda 表达式的实现本质就是内部类。先在编译器创建一个私有的静态方法,装着我们在 Lambda 表达式中需要实现的代码;然后在运行时创建一个实现了接口的内部类,在接口方法中调用编译器生成好的私有静态方法。