全网整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:400-708-3566

Java 8 动态类型语言Lambda表达式实现原理解析

Java 8支持动态语言,看到了很酷的Lambda表达式,对一直以静态类型语言自居的Java,让人看到了Java虚拟机可以支持动态语言的目标。

import java.util.function.Consumer; 
public class Lambda { 
  public static void main(String[] args) { 
    Consumer<String> c = s -> System.out.println(s); 
    c.accept("hello lambda!"); 
  } 
} 

刚看到这个表达式,感觉java的处理方式是属于内部匿名类的方式

public class Lambda { 
  static { 
    System.setProperty("jdk.internal.lambda.dumpProxyClasses", "."); 
  } 
  public static void main(String[] args) { 
    Consumer<String> c = new Consumer<String>(){ 
      @Override 
      public void accept(String s) { 
        System.out.println(s); 
      } 
      }; 
    c.accept("hello lambda"); 
  } 
} 

编译的结果应该是Lambda.class , Lambda$1.class 猜测在支持动态语言java换汤不换药,在最后编译的时候生成我们常见的方式。

但是结果不是这样的,只是产生了一个Lambda.class

反编译吧,来看看真相是什么?

javap -v -p Lambda.class 

注意  -p 这个参数 -p 参数会显示所有的方法,而不带默认是不会反编译private 的方法的

public Lambda(); 
  descriptor: ()V 
  flags: ACC_PUBLIC 
  Code: 
   stack=1, locals=1, args_size=1 
     0: aload_0 
     1: invokespecial #21         // Method java/lang/Object."<init>":()V 
     4: return 
   LineNumberTable: 
    line 3: 0 
   LocalVariableTable: 
    Start Length Slot Name  Signature 
      0    5   0 this  LLambda; 
 public static void main(java.lang.String[]); 
  descriptor: ([Ljava/lang/String;)V 
  flags: ACC_PUBLIC, ACC_STATIC 
  Code: 
   stack=2, locals=2, args_size=1 
     0: invokedynamic #30, 0       // InvokeDynamic #0:accept:()Ljava/util/function/Consumer; 
     5: astore_1 
     6: aload_1 
     7: ldc      #31         // String hello lambda 
     9: invokeinterface #33, 2      // InterfaceMethod java/util/function/Consumer.accept:(Ljava/lang/Object;)V 
    14: return 
   LineNumberTable: 
    line 8: 0 
    line 9: 6 
    line 10: 14 
   LocalVariableTable: 
    Start Length Slot Name  Signature 
      0   15   0 args  [Ljava/lang/String; 
      6    9   1   c  Ljava/util/function/Consumer; 
   LocalVariableTypeTable: 
    Start Length Slot Name  Signature 
      6    9   1   c  Ljava/util/function/Consumer<Ljava/lang/String;>; 
 private static void lambda$0(java.lang.String); 
  descriptor: (Ljava/lang/String;)V 
  flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC 
  Code: 
   stack=2, locals=1, args_size=1 
     0: getstatic   #46         // Field java/lang/System.out:Ljava/io/PrintStream; 
     3: aload_0 
     4: invokevirtual #50         // Method java/io/PrintStream.println:(Ljava/lang/String;)V 
     7: return 
   LineNumberTable: 
    line 8: 0 
   LocalVariableTable: 
    Start Length Slot Name  Signature 
      0    8   0   s  Ljava/lang/String; 
} 
SourceFile: "Lambda.java" 
BootstrapMethods: 
 0: #66 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; 
  Method arguments: 
   #67 (Ljava/lang/Object;)V 
   #70 invokestatic Lambda.lambda$0:(Ljava/lang/String;)V 
   #71 (Ljava/lang/String;)V 
InnerClasses: 
   public static final #77= #73 of #75; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles 

在这里我们发现了几个与我们常见的java不太一样的地方,由于常量定义太多了,文章中就不贴出了

1. Invokedynamic 指令

Java的调用函数的四大指令(invokevirtual、invokespecial、invokestatic、invokeinterface),通常方法的符号引用在静态类型语言编译时就能产生,而动态类型语言只有在运行期才能确定接收者类型,改变四大指令的语意对java的版本有很大的影响,所以在JSR 292 《Supporting Dynamically Typed Languages on the Java Platform》添加了一个新的指令

Invokedynamic

0: invokedynamic #30,  0             // InvokeDynamic #0:accept:()Ljava/util/function/Consumer; 

 #30 是代表常量#30 也就是后面的注释InvokeDynamic #0:accept:()Ljava/util/function/Consumer;

0 是占位符号,目前无用

2. BootstrapMethods

每一个invokedynamic指令的实例叫做一个动态调用点(dynamic call site), 动态调用点最开始是未链接状态(unlinked:表示还未指定该调用点要调用的方法), 动态调用点依靠引导方法来链接到具体的方法.  引导方法是由编译器生成, 在运行期当JVM第一次遇到invokedynamic指令时, 会调用引导方法来将invokedynamic指令所指定的名字(方法名,方法签名)和具体的执行代码(目标方法)链接起来, 引导方法的返回值永久的决定了调用点的行为.引导方法的返回值类型是java.lang.invoke.CallSite, 一个invokedynamic指令关联一个CallSite, 将所有的调用委托到CallSite当前的target(MethodHandle)
InvokeDynamic #0 就是BootstrapMethods表示#0的位置

0: #66 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; 
 Method arguments: 
  #67 (Ljava/lang/Object;)V 
  #70 invokestatic Lambda.lambda$0:(Ljava/lang/String;)V 
  #71 (Ljava/lang/String;)V 

我们看到调用了LambdaMetaFactory.metafactory 的方法

参数:

LambdaMetafactory.metafactory(Lookup, String, MethodType, MethodType, MethodHandle, MethodType)有六个参数, 按顺序描述如下

1. MethodHandles.Lookup caller : 代表查找上下文与调用者的访问权限, 使用invokedynamic指令时, JVM会自动自动填充这个参数

2. String invokedName : 要实现的方法的名字, 使用invokedynamic时, JVM自动帮我们填充(填充内容来自常量池InvokeDynamic.NameAndType.Name), 在这里JVM为我们填充为 "apply", 即Consumer.accept方法名.

3. MethodType invokedType : 调用点期望的方法参数的类型和返回值的类型(方法signature). 使用invokedynamic指令时, JVM会自动自动填充这个参数(填充内容来自常量池InvokeDynamic.NameAndType.Type), 在这里参数为String, 返回值类型为Consumer, 表示这个调用点的目标方法的参数为String, 然后invokedynamic执行完后会返回一个即Consumer实例.

4. MethodType samMethodType :  函数对象将要实现的接口方法类型, 这里运行时, 值为 (Object)Object 即 Consumer.accept方法的类型(泛型信息被擦除).#67 (Ljava/lang/Object;)V

5. MethodHandle implMethod : 一个直接方法句柄(DirectMethodHandle), 描述在调用时将被执行的具体实现方法 (包含适当的参数适配, 返回类型适配, 和在调用参数前附加上捕获的参数), 在这里为 #70 invokestatic Lambda.lambda$0:(Ljava/lang/String;)V 方法的方法句柄.

6. MethodType instantiatedMethodType : 函数接口方法替换泛型为具体类型后的方法类型, 通常和 samMethodType 一样, 不同的情况为泛型:

比如函数接口方法定义为 void accept(T t)  T为泛型标识, 这个时候方法类型为(Object)Void,  在编译时T已确定, 即T由String替换, 这时samMethodType就是 (Object)Void, 而instantiatedMethodType为(String)Void.

第4, 5, 6 三个参数来自class文件中的. 如上面引导方法字节码中Method arguments后面的三个参数就是将应用于4, 5, 6的参数.

Method arguments: 
  #67 (Ljava/lang/Object;)V 
  #70 invokestatic Lambda.lambda$0:(Ljava/lang/String;)V 
  #71 (Ljava/lang/String;)V 

我们来看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(); 
  } 

在buildCallSite的函数中

CallSite buildCallSite() throws LambdaConversionException { 
    final Class<?> innerClass = spinInnerClass(); 

函数spinInnerClass 构建了这个内部类,也就是生成了一个Lambda$$Lambda$1/716157500 这样的内部类,这个类是在运行的时候构建的,并不会保存在磁盘中,如果想看到这个构建的类,可以通过设置环境参数

System.setProperty("jdk.internal.lambda.dumpProxyClasses", "."); 

会在你指定的路径 . 当前运行路径上生成这个内部类

3.静态类

Java在编译表达式的时候会生成lambda$0静态私有类,在这个类里实现了表达式中的方法块 system.out.println(s);

private static void lambda$0(java.lang.String); 
  descriptor: (Ljava/lang/String;)V 
  flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC 
  Code: 
   stack=2, locals=1, args_size=1 
     0: getstatic   #46         // Field java/lang/System.out:Ljava/io/PrintStream; 
     3: aload_0 
     4: invokevirtual #50         // Method java/io/PrintStream.println:(Ljava/lang/String;)V 
     7: return 
   LineNumberTable: 
    line 8: 0 
   LocalVariableTable: 
    Start Length Slot Name  Signature 
      0    8   0   s  Ljava/lang/String;

当然了在上一步通过设置的jdk.internal.lambda.dumpProxyClasses里生成的Lambda$$Lambda$1.class

public void accept(java.lang.Object); 
  descriptor: (Ljava/lang/Object;)V 
  flags: ACC_PUBLIC 
  Code: 
   stack=1, locals=2, args_size=2 
    0: aload_1 
    1: checkcast   #15         // class java/lang/String 
    4: invokestatic #21         // Method Lambda.lambda$0:(Ljava/lang/String;)V 
    7: return 
  RuntimeVisibleAnnotations: 
   0: #13() 

调用了Lambda.lambda$0静态函数,也就是表达式中的函数块

总结

这样就完成的实现了Lambda表达式,使用invokedynamic指令,运行时调用LambdaMetafactory.metafactory动态的生成内部类,实现了接口,内部类里的调用方法块并不是动态生成的,只是在原class里已经编译生成了一个静态的方法,内部类只需要调用该静态方法

以上所述是小编给大家介绍的Java 8 动态类型语言Lambda表达式实现原理解析,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对网站的支持!


# java8  # lambda表达式  # 一文带你入门JDK8新特性——Lambda表达式  # Java中Lambda表达式基础及使用  # java8新特性-lambda表达式入门学习心得  # 在这里  # 返回值  # 句柄  # 实现了  # 方法来  # 小编  # 反编译  # 看到了  # 几个  # 是在  # 让人  # 换汤不换药  # 在这个  # 在此  # 不太  # 就不  # 是由  # 会在  # 可以通过  # 给大家 


相关文章: 如何在新浪SAE免费搭建个人博客?  如何在香港服务器上快速搭建免备案网站?  网页设计网站制作软件,microsoft office哪个可以创建网页?  黑客如何利用漏洞与弱口令入侵网站服务器?  哈尔滨网站建设策划,哈尔滨电工证查询网站?  Swift开发中switch语句值绑定模式  如何解决ASP生成WAP建站中文乱码问题?  如何通过山东自助建站平台快速注册域名?  如何快速建站并高效导出源代码?  建站OpenVZ教程与优化策略:配置指南与性能提升  广州美橙建站如何快速搭建多端合一网站?  建站10G流量真的够用吗?如何应对访问高峰?  微信h5制作网站有哪些,免费微信H5页面制作工具?  详解jQuery中基本的动画方法  定制建站流程解析:需求评估与SEO优化功能开发指南  微信推文制作网站有哪些,怎么做微信推文,急?  python的本地网站制作,如何创建本地站点?  建站之星×万网:智能建站系统+自助建站平台一键生成  企业网站制作费用多少,企业网站空间一般需要多大,费用是多少?  建站之星会员如何解锁更多建站功能?  ,在苏州找工作,上哪个网站比较好?  如何通过NAT技术实现内网高效建站?  如何在香港免费服务器上快速搭建网站?  东莞市网站制作公司有哪些,东莞找工作用什么网站好?  ,南京靠谱的征婚网站?  高防网站服务器:DDoS防御与BGP线路的AI智能防护方案  建站主机选购指南:核心配置与性价比推荐解析  建站之星安装路径如何正确选择及配置?  建站之星安装提示数据库无法连接如何解决?  怎么制作一个起泡网,水泡粪全漏粪育肥舍冬季氨气超过25ppm,可以有哪些措施降低舍内氨气水平?  枣阳网站制作,阳新火车站打的到仙岛湖多少钱?  如何在企业微信快速生成手机电脑官网?  如何挑选优质建站一级代理提升网站排名?  网站制作培训多少钱一个月,网站优化seo培训课程有哪些?  网站企业制作流程,用什么语言做企业网站比较好?  建站之星价格显示格式升级,你的预算足够吗?  建站中国官网:模板定制+SEO优化+建站流程一站式指南  宝华建站服务条款解析:五站合一功能与SEO优化设置指南  如何在Tomcat中配置并部署网站项目?  香港服务器网站生成指南:免费资源整合与高速稳定配置方案  如何选择适配移动端的WAP自助建站平台?  正规网站制作公司有哪些,目前国内哪家网页网站制作设计公司比较专业靠谱?口碑好?  上海网站制作开发公司,上海买房比较好的网站有哪些?  如何在云服务器上快速搭建个人网站?  c++怎么使用类型萃取type_traits_c++ 模板元编程类型判断【方法】  如何在IIS中新建站点并配置端口与物理路径?  在线流程图制作网站手机版,谁能推荐几个好的CG原画资源网站么?  如何快速生成凡客建站的专业级图册?  如何在建站之星绑定自定义域名?  ,怎么用自己头像做动态表情包? 

您的项目需求

*请认真填写需求信息,我们会在24小时内与您取得联系。