【修炼内功】[Java8] Lambda究竟是不是匿名类的语法糖

栏目: 编程语言 · Java · 发布时间: 5年前

内容简介:本文已收录【修炼内功】跃迁之路初次接触Java8的时候感觉Lambda表达式很神奇(

本文已收录【修炼内功】跃迁之路

【修炼内功】[Java8] Lambda究竟是不是匿名类的语法糖

初次接触 Java 8的时候感觉Lambda表达式很神奇( Lambda表达式带来的编程新思路 ),但又总感觉它就是匿名类或者内部类的语法糖而已,只是语法上更为简洁罢了,如同以下的代码

public class Lambda {
    private static void hello(String name, Consumer<String> printer) {
        printer.accept(name);
    }

    public static void main(String[] args) {
        hello("lambda", (name) -> System.out.println("Hello " + name));
        hello("匿名类", new Consumer<String> () {
            @Override
            public void accept(String name) {
                System.out.println("Hello " + name);
            }
        });
        hello("内部类", new SupplierImpl());
    }

    static class SupplierImpl implements Consumer<String> {
        @Override
        public void accept(String name) {
            System.out.println("Hello " + name);
        }
    }
}

编译后会产生三个文件

【修炼内功】[Java8] Lambda究竟是不是匿名类的语法糖

虽然从使用效果来看,Lambda与匿名类或者内部类有相似之处(当然也有很大不同,如this指针等 Lambda表达式里的"陷阱" ),但从编译结果来看,并不能简单地将Lambda与匿名类/内部类划等号

简单查看Lambda字节码 javap -p Lambda

【修炼内功】[Java8] Lambda究竟是不是匿名类的语法糖

Java编译器自动帮我们生成了方法 lambda$main$0 ,我们有理由相信,Lambda表达式内的逻辑就封装在此函数内

【修炼内功】[Java8] Lambda究竟是不是匿名类的语法糖

生成的方法 lambda$main$0 又是如何被调用的呢?

【修炼内功】[Java8] Lambda究竟是不是匿名类的语法糖

Lambda的调用使用了 invokedynamic 指令,虚拟机视角的方法调用一文中已经详细介绍了 invokedynamic ,但这里还是看不出 invokedynamic 指令与 lambda$main$0 方法之间到底是如何关联起来的, invokedynamic 指令的启动函数( BootstrapMethod )与调用点( CallSite )又在哪里?

其实仔细查看字节码的话可以发下,编译器还会额外生成一个内部类

【修炼内功】[Java8] Lambda究竟是不是匿名类的语法糖

仔细查看内部类的逻辑,是不是像极了虚拟机视角的方法调用一文中所提invokedynamic的运行过程

  1. 在第一次执行invokedynamic时,JVM虚拟机会调用该指令所对应的启动方法( BootstrapMethod )来生成调用点
  2. 启动方法( BootstrapMethod )由方法句柄来指定( MH_BootstrapMethod )
  3. 启动方法接受三个固定的参数,分别为 Lookup实例、指代目标方法名的字符串及该调用点能够链接的方法句柄类型
  4. 将调用点绑定至该invokedynamic指令中,之后的运行中虚拟机会直接调用绑定的调用点所链接的方法句柄

为了验证此想法,可以执行 java -Djdk.internal.lambda.dumpProxyClasses Lambda 用来导出内部类

【修炼内功】[Java8] Lambda究竟是不是匿名类的语法糖

跟踪内部类的运行可以发现,在执行lambda表达式的时候会调用 MethodHandleNatives.linkCallSite 方法来生成并链接到调用点

// Up-calls from the JVM.
// These must NOT be public.

/**
  * The JVM is linking an invokedynamic instruction.  Create a reified call site for it.
  */
static MemberName linkCallSite(Object callerObj,
                               Object bootstrapMethodObj,
                               Object nameObj, Object typeObj,
                               Object staticArguments,
                               Object[] appendixResult) {
    MethodHandle bootstrapMethod = (MethodHandle)bootstrapMethodObj;
    Class<?> caller = (Class<?>)callerObj;
    String name = nameObj.toString().intern();
    MethodType type = (MethodType)typeObj;
    if (!TRACE_METHOD_LINKAGE)
        return linkCallSiteImpl(caller, bootstrapMethod, name, type,
                                staticArguments, appendixResult);
    return linkCallSiteTracing(caller, bootstrapMethod, name, type,
                               staticArguments, appendixResult);
}
static MemberName linkCallSiteImpl(Class<?> caller,
                                   MethodHandle bootstrapMethod,
                                   String name, MethodType type,
                                   Object staticArguments,
                                   Object[] appendixResult) {
    CallSite callSite = CallSite.makeSite(bootstrapMethod,
                                          name,
                                          type,
                                          staticArguments,
                                          caller);
    if (callSite instanceof ConstantCallSite) {
        appendixResult[0] = callSite.dynamicInvoker();
        return Invokers.linkToTargetMethod(type);
    } else {
        appendixResult[0] = callSite;
        return Invokers.linkToCallSiteMethod(type);
    }
}

caller调用lambda 方法的类 Lambda [Class]

bootstrapMethod 为启动方法的句柄 [MethodHandler]

name 为lambda表达式实际类型中需要执行的方法名 acccept (Consumer.accept)

type 为生成的方法类型 ()Consumer [MethodType]

staticArguments 中包含了lambda方法的方法句柄 [MethodHandler] 及方法类型 [MethodType]

CallSite.makeSite 方法会生成调用点,最终调用如class文件中所示的 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();
}

简单来将,所生成内部类作用,是为了生成调用点,并链接到invokedynamic指令,以便动态调用

如果一个类中,多次使用lambda表达式,会生成多少个方法,又会生成多少个内部类?

public class Lambda {
    private static void hello(String name, Consumer<String> printer) {
        printer.accept(name);
    }

    public static void main(String[] args) {
        hello("lambda1", (name) -> System.out.println("Hello " + name));
        hello("lambda2", (name) -> System.out.println("Hello " + name));
        new Thread(() -> {
            System.out.println("thread");
        }).start();
    }
}

上述示例中共使用了三处、两种(Consumer、Runnable)Lambda表达式,编译后查看class文件

【修炼内功】[Java8] Lambda究竟是不是匿名类的语法糖

对于每一个lambda表达式,都会生成一个静态的私有方法

再查看内部类

【修炼内功】[Java8] Lambda究竟是不是匿名类的语法糖

只会生成一个内部类,但存在三个方法,每个方法对应一个lambda私有方法,用以生成对应的调用点绑定到相应的invokedynamic指令上

结合以上我们可以总结出:

  • lambda表达式会被编译为invokedynamic指令
  • 每一个lambda表达式的实现逻辑均会被封装为一个静态私有方法
  • 只要存在lambda表达式调用,便会生成一个内部类
  • 内部类中每一个方法对应一个lambda表达式所生成的静态私有方法,内部类中的方法用以生成对应的调用点绑定到相应的invokedynamic指令上

这也解释了为什么lambda中的this指针指向的是 周围的类 (定义该Lambda表达式时所处的类) ( Lambda表达式里的"陷阱" )

所以,lambda表达式确实是语法糖,但并不是匿名类/内部类的语法糖


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

黑客秘笈

黑客秘笈

[美]彼得·基姆 / 徐文博、成明遥 / 人民邮电出版社 / 2015-7-1 / 45.00

所谓的渗透测试,就是借助各种漏洞扫描工具,通过模拟黑客的攻击方法,来对网络安全进行评估。 本书采用大量真实案例和集邮帮助的建议讲解了在渗透测试期间会面临的一些障碍,以及相应的解决方法。本书共分为10章,其内容涵盖了本书所涉的攻击机器/工具的安装配置,网络扫描,漏洞利用,人工地查找和搜索Web应用程序的漏洞,攻陷系统后如何获取更重要的信息,社工方面的技巧,物理访问攻击,规避杀毒软件的方法,破解......一起来看看 《黑客秘笈》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

随机密码生成器
随机密码生成器

多种字符组合密码