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

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

027c9491c39ed6f7355fa1faac96775a.png

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

public class Lambda {

private static void hello(String name, Consumer printer) {

printer.accept(name);

}

public static void main(String[] args) {

hello("lambda", (name) -> System.out.println("Hello " + name));

hello("匿名类", new Consumer () {

@Override

public void accept(String name) {

System.out.println("Hello " + name);

}

});

hello("内部类", new SupplierImpl());

}

static class SupplierImpl implements Consumer {

@Override

public void accept(String name) {

System.out.println("Hello " + name);

}

}

}

编译后会产生三个文件

ea3c3481f5c9390dcc593088ba62f9b7.png

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

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

fccee4e50f208c3c013eda0af6629c1a.png

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

8cfcd692bdd7994f4ac1e68e016f23c8.png

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

233e04763d1cca6792466f4825aaa55f.png

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

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

7d9706d8668bb9dca839b5117c6f584e.png

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

在第一次执行invokedynamic时,JVM虚拟机会调用该指令所对应的启动方法( BootstrapMethod )来生成调用点

启动方法( BootstrapMethod )由方法句柄来指定( MH_BootstrapMethod )

启动方法接受三个固定的参数,分别为 Lookup实例、指代目标方法名的字符串及该调用点能够链接的方法句柄类型

将调用点绑定至该invokedynamic指令中,之后的运行中虚拟机会直接调用绑定的调用点所链接的方法句柄

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

b4d50ba3ce423e74073a952436854c2a.png

跟踪内部类的运行可以发现,在执行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 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文件

637a59ffa535d33dd5b14f99cc0e4c53.png

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

再查看内部类

c35d1d3273e26bf71507ec6ac0f27715.png

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

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

lambda表达式会被编译为invokedynamic指令

每一个lambda表达式的实现逻辑均会被封装为一个静态私有方法

只要存在lambda表达式调用,便会生成一个内部类

内部类中每一个方法对应一个lambda表达式所生成的静态私有方法,内部类中的方法用以生成对应的调用点绑定到相应的invokedynamic指令上

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值