在看字节码细节之前, 先来了解一下 lambda 表达式如何脱糖(desugar).lambda 的语法糖在编译后的字节流 Class 文件中, 会通过 invokedynamic 指令指向一个 Bootstrap 方法(下文中部分会称作 "引导方法"), 这个方法就是 java.lang.invoke.LambdaMetafactory 中的一个静态方法. 通过 debug 的方式, 就可以看到该方法的执行, 此方法源码如下:
- 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();
- }
在运行时期, 虚拟机会通过调用这个方法来返回一个 CallSite(调用点)对象. 简述一下方法的执行过程, 首先, 初始化一个 InnerClassLambdaMetafactory 对象, 这个对象的 buildCallSite 方法会将 Lambda 表达式先转化成一个内部类, 这个内部类是 MethodHandles.Lookup caller 的一个内部类, 也即包含此 Lambda 表达式的类的内部类. 这个内部类是通过字节码生成技术 (jdk.internal.org.objectweb.asm) 生成, 再通过 UNSAFE 类加载到 JVM. 然后再返回绑定此内部类的 CallSite 对象, 这个过程的源码也可以看一下:
- CallSite buildCallSite() throws LambdaConversionException {
- // 通过字节码生成技术 (jdk asm) 生成代表 lambda 表达式体信息的一个内部类的 Class 对象, 因为是运行期生成, 所以在编译后的字节码信息中并没有这个内部类的字节流信息.
- final Class<?> innerClass = spinInnerClass();
- // incokedType 即 lambda 表达式的调用方法类型, 如下面示例中的 Consumer 方法
- if (invokedType.parameterCount() == 0) {
- final Constructor<?>[] ctrs = AccessController.doPrivileged(
- new PrivilegedAction<Constructor<?>[]>() {
- @Override
- public Constructor<?>[] run() {
- Constructor<?>[] ctrs = innerClass.getDeclaredConstructors();
- if (ctrs.length == 1) {
- // 表示 lambda 表达式的内部类是私有的, 所以需要获取这个内部类的访问权限.
- ctrs[0].setAccessible(true);
- }
- return ctrs;
- }
- });
- if (ctrs.length != 1) {
- throw new LambdaConversionException("Expected one lambda constructor for"
- + innerClass.getCanonicalName() + ", got" + ctrs.length);
- }
- try {
- // 通过构造函数的 newInstance 方法, 创建一个内部类对象
- Object inst = ctrs[0].newInstance();
- // MethodHandles.constant 方法将这个内部类对象的信息组装并绑定到一个 MethodHandle 对象, 作为 ConstantCallSite 的构造函数的参数 "target" 返回. 后面对于 Lambda 表达式的调用, 都会通过 MethodHandle 直接调用, 不需再次生成 CallSite.
- return new ConstantCallSite(MethodHandles.constant(samBase, inst));
- }
- catch (ReflectiveOperationException e) {
- throw new LambdaConversionException("Exception instantiating lambda object", e);
- }
- } else {
- try {
- UNSAFE.ensureClassInitialized(innerClass);
- return new ConstantCallSite(
- MethodHandles.Lookup.IMPL_LOOKUP
- .findStatic(innerClass, NAME_FACTORY, invokedType));
- }
- catch (ReflectiveOperationException e) {
- throw new LambdaConversionException("Exception finding constructor", e);
- }
- }
- }
这个过程将生成一个代表 lambda 表达式信息的内部类(也就是方法第一行的 innerClass, 这个类是一个 functional 类型接口的实现类), 这个内部类的 Class 字节流是通过 jdk asm 的 ClassWriter,MethodVisitor, 生成, 然后再通过调用 Constructor.newInstance 方法生成这个内部类的对象, 并将这个内部类对象绑定给一个 MethodHandle 对象, 然后这个 MethodHandle 对象传给 CallSite 对象(通过 CallSite 的构造函数赋值). 所以这样就完成了一个将 lambda 表达式转化成一个内部类对象, 然后将内部类通过 MethodHandle 绑定到一个 CallSite 对象. CallSite 对象就相当于 lambda 表达式的一个勾子. 而 invokedynamic 指令就链接到这个 CallSite 对象来实现运行时绑定, 也即 invokedynamic 指令在调用时, 会通过这个勾子找到 lambda 所代表的一个 functional 接口对象(也即 MethodHandle 对象). 所以 lambda 的脱糖也就是在运行期通过 Bootstrap method 的字节码信息, 转化成一个 MethodHandle 的过程.
通过打印 consumer 对象的 className(greeter.getClass().getName())可以得到结果是 eight.Functionnal$$Lambda$1/659748578 前面字符是 Lambda 表达式的 ClassName, 后面的 659748578 是刚才所述内部类的 hashcode 值.
来源: http://www.jianshu.com/p/92484afb62b3