java中lambda用法,lambda怎么实现的
这篇文章给大家带来了一些关于java的知识,主要是关于如何看待Lambda源代码。使用Lambda expression可以优化代码很多,你可以用几行代码做很多事情。下面就一起来看看吧,希望对你有帮助。
如何解决写爬虫IP受阻的问题?立即使用。
众所周知,Java8中新增了Lambda表达式。使用Lambda表达式可以优化很多代码,很多事情只用几行代码就可以完成。本章以Lambda为例。第一节解释其底层执行原理,第二节解释Lambda流在工作中的常见姿势。
1、Demo
首先来看一个Lambda表达式的演示,如下图:
代码比较简单,就是一个新线程打印一句话,但是对于图中的代码()-system . out . println(" lambda is run ")估计很多同学都很困惑。Java是如何识别这类代码的?
如果改成匿名内部类,就很清楚了,大家都能看懂,如下图:
那是否意味着()-system . out . println(" lambda is run ")形式的代码实际上建立了一个内部类?其实这是最简单的Lambda表达式。我们无法通过IDEA看到源代码及其底层结构。这里我们将介绍几种方法来查看它的底层实现。
2、异常判断法
我们可以在代码执行过程中主动抛出异常,并打印出堆栈,堆栈会解释其运行轨迹。一般这种方法简单高效,基本上很多情况下我们都能看到隐藏的代码。我们来试试,如下图:
从异常的堆栈中,我们可以看到JVM自动为当前类创建一个内部类(在错误堆栈中表示有内部类)。在执行过程中,内部类的代码抛出异常,但是这里显示的代码是未知来源,我们也无法调试。一般来说,异常可以暴露代码执行的路径,我们可以做一个断点,再运行一次。但是对于Lambda表达式,我们只通过异常判断方法知道有异常。
3、javap 命令法
javap是Java自带的查看类字节码文件的工具。安装了Java基础环境的电脑可以直接执行javap命令,如下图所示:
在选项中,我们主要使用命令-v -verbose,它可以完整地输出字节码文件的内容。
接下来,我们使用javap命令查看Lambda.class文件。在讲解的过程中,我们会带一些关于类文件的知识。
我们在命令窗口中找到Lambda.class的位置,执行命令:javap -verbose Lambda.class,然后你会看到一长串东西,叫做汇编指令。接下来我们就来一一解释一下(参考资料都来自Java虚拟机规范,就不一一引用了):
我们很容易在汇编指令中找到一长串以常量池开头的类型。我们称之为constant pool,官方英文名称是Run-Time Constant Pool。我们简单的把它理解为一个充满常数的表,表中包含了编译时明确的数字和单词,类、方法和字段的类型信息等等。table中的每个元素都被称为cpinfo,cpinfo由一个惟一的标识符(标签)名组成。目前,总共有以下标签类型:
贴一张我们分析过的部分图片:
图中常量池一词表示当前信息为常量池;
每行是一个cp_info,第一列的#1在常量池下标1的位置;
每行的第二列是cp_info的唯一标识符(标签)。比如Methodref对应上表中的CONSTANT_Methodref(上表中value对应10的标签),表示当前行代表方法的描述信息,比如方法的名称、参数类型、参数类型等。具体含义可以在Java虚拟机规范中找到。Methodref的截图如下:
每行第三列,如果是具体值,直接显示具体值,如果是复数值,显示cp_info的引用。比如图中的两个红点分别指的是位置13和14的cp_info,其中13表示方法名为init,14表示方法没有返回值。当组合在一起时,它指示方法的名称和返回类型,这是一个无参数的构造函数。
每行的第四列是具体值。
对于更重要的cp_info类型,我们来解释一下它的含义:
InvokeDynamic代表一种动态调用方法,我们后面会详细解释;Ref表示字段的描述信息,例如字段的名称和类型;NameAndType是字段和方法类型的描述;MethodHandle方法句柄,动态调用方法的统称。编译时,我们不知道具体是哪个方法,但运行时肯定会知道调用的是哪个方法;MethodType是一个动态方法类型,它的方法类型只有在动态运行时才知道。我们从上图中标记为红色的三个地方找到了类似LJAVA/lang/invoke/method handles $ lookup,Java/lang/invoke/lambdametacfactory . metafactory这样的代码。MethodHandles和LambdaMetafactory都是java.lang.invoke包下的重要方法,主要实现动态语言的功能。我们知道java语言属于静态编译语言,以及类、方法、字段等的类型。已经在编译时确定,而invoke实现了一种动态语言,这意味着类、方法和字段的类型在编译时是未知的,只有在运行时才知道。
比如这行代码:runnable runnable=()-system . out . println(" lambda is run ");当编译器编译()时,这个括号编译器不知道它是做什么的。只有在运行时,它才知道自己代表Runnable.run()方法。invoke包中有许多类,所有这些类都是为了表示这些()。我们称它们为MethodHandlers。编译时,编译器只知道这是一个方法处理程序,不知道实际执行的是什么方法。只有执行的时候,那么问题来了。当JVM执行时,它如何知道方法处理程序()实际上执行了Runnable.run()方法?
首先,让我们看看简单方法的汇编指令:
从上图可以看出,简单方法中的()-system . out . println(" lambda is run ")代码中的()其实就是Runnable.run方法。
我们追溯到# 2常量池,也就是上图中的红色1。InvokeDynamic表示这是一个动态调用,调用两个常量池的cp_info,位置是#0:#37。往下看#37表示是//run:()Ljava/lang/Runnable,表示JVM实际执行时,我们需要动态调用Runnable.run()方法。从汇编指令中,我们可以看到()实际上是Runnable.run()。我们来调试一下证明一下。
我们在上图的三个地方发现了LambdaMetafactory.metafactory这几个字。通过查询官方文档,我们了解到这个方法是执行时链接到真实代码的关键,所以我们在metafactory方法中做了一个断点来调试,如下图所示:
Metafactory方法参数caller表示动态调用实际发生的位置,invokedName表示调用方法的名称,invokedType表示调用的多个参数,samMethodType表示具体实现者的参数,implMethod表示实际实现者,instantiatedMethodType相当于implMethod。
综上所述:
1:从汇编指令的简单方法可以看出,Runnable.run方法会被执行;
2.在实际运行时,JVM遇到simple方法的invokedynamic指令时,会动态调用LambdaMetafactory.metafactory方法,执行具体的Runnable.run方法。
所以Lambda表达式值的具体执行可以归结为invokedynamic JVM指令。正是因为有了这条指令,才能在动态运行时找到具体要执行的代码,虽然编译时我们不知道该怎么做。
然后我们来看看汇编指令输出的结尾,我们找到了异常判断方法中找到的内部类,如下图所示:
上图有很多箭头,把当前内类的所有信息都一层一层的表达清楚了。
4、总结
总结一下,Lambda表达式执行主要依靠invokedynamic的JVM指令。我们演示的类的完整路径是:demo.eight.Lambda有兴趣的同学可以自己试试。
不啰嗦了,文末,期待三人行!
推荐:《java视频教程》以上是Java技能的总结。更多详情请关注我们的其他相关文章。
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。