Java框架!深入底层原理—带你看透Lambda表达式的本质

Posted 程序员超时空

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java框架!深入底层原理—带你看透Lambda表达式的本质相关的知识,希望对你有一定的参考价值。

=====================================================================

嗨咯,大家好,今天给带大家深入底层原理看透Lambda表达式的本质,如果本文对你有帮助,记得一键三连一下!!

知识点

======================================================================

Lambda的原理


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

Lambda的案例



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!");

	}

} 

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 参数会显示所有的方法,而不带默认是不会反编译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 

重点关注方法

Invokedynamic

  • Java的调用函数的四大指令(invokevirtual、invokespecial、invokestatic、invokeinterface),通常方法的符号引用在静态类型语言编译时就能产生。

  • 动态类型语言只有在运行期才能确定接收者类型,改变四大指令的语意对java的版本有很大的影响,所以在JSR 292 《Supporting Dynamically Typed Languages on the Java Platform》添加了一个新的指令:Invokedynamic。


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

0: invokedynamic #30,0 

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

  • 0 是占位符号,目前无用

BootstrapMethods

每一个invokedynamic指令的实例叫做一个动态调用点(dynamic call site),动态调用点最开始是未链接状态(unlinked):表示还未指定该调用点要调用的方法), 动态调用点依靠引导方法来链接到具体的方法。

引导方法是由编译器生成,在运行期当JVM第一次遇到invokedynamic指令时, 会调用引导方法来将invokedynamic指令所指定的名字(方法名,方法签名)和具体的执行代码(目标方法)链接起来, 引导方法的返回值永久的决定了调用点的行为。

CallSite

引导方法的返回值类型是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 

public static CallSite metafactory(MethodHandles.Lookup caller,

                                   String invokedName,

                                   MethodType invokedType,

最后的内容

在开头跟大家分享的时候我就说,面试我是没有做好准备的,全靠平时的积累,确实有点临时抱佛脚了,以至于我自己还是挺懊恼的。(准备好了或许可以拿个40k,没做准备只有30k+,你们懂那种感觉吗)

如何准备面试?

1、前期铺垫(技术沉积)

程序员面试其实是对于技术的一次摸底考试,你的技术牛逼,那你就是大爷。大厂对于技术的要求主要体现在:基础,原理,深入研究源码,广度,实战五个方面,也只有将原理理论结合实战才能把技术点吃透。

下面是我会看的一些资料笔记,希望能帮助大家由浅入深,由点到面的学习Java,应对大厂面试官的灵魂追问,有需要的话就戳这里:蓝色传送门打包带走吧。

这部分内容过多,小编只贴出部分内容展示给大家了,见谅见谅!

  • Java程序员必看《Java开发核心笔记(华山版)》

  • Redis学习笔记

  • Java并发编程学习笔记

四部分,详细拆分并发编程——并发编程+模式篇+应用篇+原理篇

  • Java程序员必看书籍《深入理解 ava虚拟机第3版》(pdf版)

  • 大厂面试必问——数据结构与算法汇集笔记

其他像Spring,SpringBoot,SpringCloud,SpringCloudAlibaba,Dubbo,Zookeeper,Kafka,RocketMQ,RabbitMQ,Netty,mysql,Docker,K8s等等我都整理好,这里就不一一展示了。

2、狂刷面试题

技术主要是体现在平时的积累实用,面试前准备两个月的时间再好好复习一遍,紧接着就可以刷面试题了,下面这些面试题都是小编精心整理的,贴给大家看看。

①大厂高频45道笔试题(智商题)

②BAT大厂面试总结(部分内容截图)

③面试总结

3、结合实际,修改简历

程序员的简历一定要多下一些功夫,尤其是对一些字眼要再三斟酌,如“精通、熟悉、了解”这三者的区别一定要区分清楚,否则就是在给自己挖坑了。当然不会包装,我可以将我的简历给你参考参考,如果还不够,那下面这些简历模板任你挑选:

以上分享,希望大家可以在金三银四跳槽季找到一份好工作,但千万也记住,技术一定是平时工作种累计或者自学(或报班跟着老师学)通过实战累计的,千万不要临时抱佛脚。

另外,面试中遇到不会的问题不妨尝试讲讲自己的思路,因为有些问题不是考察我们的编程能力,而是逻辑思维表达能力;最后平时要进行自我分析与评价,做好职业规划,不断摸索,提高自己的编程能力和抽象思维能力。

56101)]

③面试总结

[外链图片转存中…(img-HzgInyHw-1628135656102)]

[外链图片转存中…(img-6V0jXp6C-1628135656103)]

3、结合实际,修改简历

程序员的简历一定要多下一些功夫,尤其是对一些字眼要再三斟酌,如“精通、熟悉、了解”这三者的区别一定要区分清楚,否则就是在给自己挖坑了。当然不会包装,我可以将我的简历给你参考参考,如果还不够,那下面这些简历模板任你挑选:

[外链图片转存中…(img-eLTyBqt3-1628135656104)]

以上分享,希望大家可以在金三银四跳槽季找到一份好工作,但千万也记住,技术一定是平时工作种累计或者自学(或报班跟着老师学)通过实战累计的,千万不要临时抱佛脚。

另外,面试中遇到不会的问题不妨尝试讲讲自己的思路,因为有些问题不是考察我们的编程能力,而是逻辑思维表达能力;最后平时要进行自我分析与评价,做好职业规划,不断摸索,提高自己的编程能力和抽象思维能力。

以上文章中,提及到的所有的笔记内容、面试题等资料,均可以免费分享给大家学习,有需要的话就戳这里打包带走吧。

以上是关于Java框架!深入底层原理—带你看透Lambda表达式的本质的主要内容,如果未能解决你的问题,请参考以下文章

带你深入剖析 synchronized 的底层原理

深入浅出!带你重学Java—ArrayList

深入理解 Lambda 表达式

Java技术指南带你深入理解和认识SPI运作机制「原理和源码篇」

很多小伙伴不太了解ORM框架的底层原理,这不,冰河带你10分钟手撸一个极简版ORM框架(赶快收藏吧)

老杜带你学Ajax,轻松掌握ajax底层实现原理