首页 > 编程 > Java > 正文

java8 lambda表达式原理

2019-11-08 02:55:04
字体:
来源:转载
供稿:网友

java8 lambda表达式原理

java8已经推出有一段时间了,相信有不少公司已经把jdk升级到8了,每次jdk的升级都会带来一些性能以及应用上的优化,比如8移出了永久区,java.lang.OutOfMemoryError: PermGen space离我们而去,以及一些新的语法糖lambda,stream,默认方法等等,本文就来说说lambda表达式

lambda表达式写法

基本语法: (parameters) -> exPRession 或 (parameters) ->{ statements; }

左边为传入参数,右边为执行代码,代表一个函数式接口的实现

lambda例子

此处不多写,贴出其他人的博客,请见Java8 lambda表达式10个示例

lambda表达式与旧的api对比

对比 codepublic class LambdaTest { public static void main(String[] args) { ArrayList<Integer> integers = new ArrayList<Integer>() ; integers.add(9); integers.add(8); integers.add(7); integers.add(6); integers.sort(new Comparator<Integer>() { public int compare(Integer o1, Integer o2) { return o1.compareTo(o2); } }); System.out.println("匿名内部类排序输出:"+integers); integers.sort((o1,o2)->o1.compareTo(02)); System.out.println("lambda1表达式排序输出:"+integers); integers.sort(Integer::compareTo); System.out.println("lambda2表达式排序输出:"+integers); }}对比 输出匿名内部类排序输出:[6, 7, 8, 9]lambda1表达式排序输出:[6, 7, 8, 9]lambda2表达式排序输出:[6, 7, 8, 9]编译后的文件 这里写图片描述 从上面看到,生成了两个class文件,一个是LambdaTest类的class文件,一个是Comparator匿名内部类的class文件,lambda表达式并未生成匿名内部类class文件,也就是说java8并不是靠编译器将lambda转换为匿名内部类,lambda脱糖过程稍后描述。

lambda原理

在java8中每一个Lambda表达式必须有一个函数式接口与之对应,那么函数式接口是什么呢?

什么是函数式接口

函数式接口(functional interface)简单来说就是只包含一个抽象方法的普通接口,java.lang.Runnable、java.util.Comparator都是函数式接口,java8提供了java.lang.FunctionalInterface注解进行标准,但是是非必须的,jdk会自动识别函数式接口。函数式接口可以被隐式转换为lambda表达式。

函数式与lambda表达式关系实例

正确实例public class LambdaTest2 { public interface TestInterface{ public void test1(); } public static void doSomething(TestInterface test){ test.test1(); } public static void main(String[] args) { doSomething(()-> System.out.println("HelloWorld")); }}

输出HelloWorld

错误实例public class LambdaTest3 { public interface TestInterface{ public void test1(); public void test2(int a); } public static void doSomething(TestInterface test){ test.test1(); } public static void main(String[] args) { doSomething(()-> System.out.println("HelloWorld")); }}

编译错误: Error:(19, 21) java: 不兼容的类型: com.chen.LambdaTest3.TestInterface 不是函数接口 在 接口 com.chen.LambdaTest3.TestInterface 中找到多个非覆盖抽象方法

那么lambda是怎么跟函数式接口对应的呢

来看一下LambdaTest2的字节码

Classfile /F:/workspace/java8-test/target/classes/com/chen/LambdaTest2.class Last modified 2017-2-18; size 1376 bytes md5 checksum 5de06d632caf9ead069b79cf854411b9 Compiled from "LambdaTest2.java"public class com.chen.LambdaTest2 minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPERConstant pool: #1 = Methodref #9.#31 // java/lang/Object."<init>":()V #2 = InterfaceMethodref #10.#32 // com/chen/LambdaTest2$TestInterface.test1:()V #3 = InvokeDynamic #0:#37 // #0:test1:()Lcom/chen/LambdaTest2$TestInterface; #4 = Methodref #8.#38 // com/chen/LambdaTest2.doSomething:(Lcom/chen/LambdaTest2$TestInterface;)V #5 = Fieldref #39.#40 // java/lang/System.out:Ljava/io/PrintStream; #6 = String #41 // HelloWorld #7 = Methodref #42.#43 // java/io/PrintStream.println:(Ljava/lang/String;)V #8 = Class #44 // com/chen/LambdaTest2 #9 = Class #45 // java/lang/Object #10 = Class #46 // com/chen/LambdaTest2$TestInterface #11 = Utf8 TestInterface #12 = Utf8 InnerClasses #13 = Utf8 <init> #14 = Utf8 ()V #15 = Utf8 Code #16 = Utf8 LineNumberTable #17 = Utf8 LocalVariableTable #18 = Utf8 this #19 = Utf8 Lcom/chen/LambdaTest2; #20 = Utf8 doSomething #21 = Utf8 (Lcom/chen/LambdaTest2$TestInterface;)V #22 = Utf8 test #23 = Utf8 Lcom/chen/LambdaTest2$TestInterface; #24 = Utf8 main #25 = Utf8 ([Ljava/lang/String;)V #26 = Utf8 args #27 = Utf8 [Ljava/lang/String; #28 = Utf8 lambda$main$0 #29 = Utf8 SourceFile #30 = Utf8 LambdaTest2.java #31 = NameAndType #13:#14 // "<init>":()V #32 = NameAndType #47:#14 // test1:()V #33 = Utf8 BootstrapMethods #34 = MethodHandle #6:#48 // 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; #35 = MethodType #14 // ()V #36 = MethodHandle #6:#49 // invokestatic com/chen/LambdaTest2.lambda$main$0:()V #37 = NameAndType #47:#50 // test1:()Lcom/chen/LambdaTest2$TestInterface; #38 = NameAndType #20:#21 // doSomething:(Lcom/chen/LambdaTest2$TestInterface;)V #39 = Class #51 // java/lang/System #40 = NameAndType #52:#53 // out:Ljava/io/PrintStream; #41 = Utf8 HelloWorld #42 = Class #54 // java/io/PrintStream #43 = NameAndType #55:#56 // println:(Ljava/lang/String;)V #44 = Utf8 com/chen/LambdaTest2 #45 = Utf8 java/lang/Object #46 = Utf8 com/chen/LambdaTest2$TestInterface #47 = Utf8 test1 #48 = Methodref #57.#58 // 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; #49 = Methodref #8.#59 // com/chen/LambdaTest2.lambda$main$0:()V #50 = Utf8 ()Lcom/chen/LambdaTest2$TestInterface; #51 = Utf8 java/lang/System #52 = Utf8 out #53 = Utf8 Ljava/io/PrintStream; #54 = Utf8 java/io/PrintStream #55 = Utf8 println #56 = Utf8 (Ljava/lang/String;)V #57 = Class #60 // java/lang/invoke/LambdaMetafactory #58 = NameAndType #61:#64 // 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; #59 = NameAndType #28:#14 // lambda$main$0:()V #60 = Utf8 java/lang/invoke/LambdaMetafactory #61 = Utf8 metafactory #62 = Class #66 // java/lang/invoke/MethodHandles$Lookup #63 = Utf8 Lookup #64 = Utf8 (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; #65 = Class #67 // java/lang/invoke/MethodHandles #66 = Utf8 java/lang/invoke/MethodHandles$Lookup #67 = Utf8 java/lang/invoke/MethodHandles{ public com.chen.LambdaTest2(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 8: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/chen/LambdaTest2; public static void doSomething(com.chen.LambdaTest2$TestInterface); descriptor: (Lcom/chen/LambdaTest2$TestInterface;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokeinterface #2, 1 // InterfaceMethod com/chen/LambdaTest2$TestInterface.test1:()V 6: return LineNumberTable: line 15: 0 line 16: 6 LocalVariableTable: Start Length Slot Name Signature 0 7 0 test Lcom/chen/LambdaTest2$TestInterface; public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=1, locals=1, args_size=1 0: invokedynamic #3, 0 // InvokeDynamic #0:test1:()Lcom/chen/LambdaTest2$TestInterface; 5: invokestatic #4 // Method doSomething:(Lcom/chen/LambdaTest2$TestInterface;)V 8: return LineNumberTable: line 18: 0 line 19: 8 LocalVariableTable: Start Length Slot Name Signature 0 9 0 args [Ljava/lang/String;}SourceFile: "LambdaTest2.java"InnerClasses: public static #11= #10 of #8; //TestInterface=class com/chen/LambdaTest2$TestInterface of class com/chen/LambdaTest2 public static final #63= #62 of #65; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandlesBootstrapMethods: 0: #34 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: #35 ()V #36 invokestatic com/chen/LambdaTest2.lambda$main$0:()V #35 ()V

其中main方法

public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=1, locals=1, args_size=1 0: invokedynamic #3, 0 // InvokeDynamic #0:test1:()Lcom/chen/LambdaTest2$TestInterface; 5: invokestatic #4 // Method doSomething:(Lcom/chen/LambdaTest2$TestInterface;)V 8: return LineNumberTable: line 18: 0 line 19: 8 LocalVariableTable: Start Length Slot Name Signature 0 9 0 args [Ljava/lang/String;

执行了三条指令 invokedynamic、invokestatic、return

第一条是lambda表达式转化为函数式接口TestInterface第二条执行doSomething方法第三条退出main方法

invokedynamic指令是在jvm7中新增的,invokedynamic出现的位置代表一个动态调用点 invokedynamic指令后面会跟一个指向常量池的调用点限定符,这个限定符会被解析为一个动态调用点。 调用点限定符的符号引用为CONSTANT_InvokeDynamic_info结构

CONSTANT_InvokeDynamic_info{ u1 tag; u2 bootstrap_method_attr_index; u2 name_and_type_index; }

依据这个可以找到对应的动态调用引导方法Java.lang.invoke.CallSite

此处invokedynamic后面跟的是常量#3,#3指向#0和#37,#37代表TestInterface接口的test1方法

#3 = InvokeDynamic #0:#37 // #0:test1:()Lcom/chen/LambdaTest2$TestInterface;#37 = NameAndType #47:#50 // test1:()Lcom/chen/LambdaTest2$TestInterface;

而#0在字节码最后的BootstrapMethods中,Method arguments#36代表这个lambda表达式调用代码

BootstrapMethods: 0: #34 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: #35 ()V #36 invokestatic com/chen/LambdaTest2.lambda$main$0:()V #35 ()V

根据BootstrapMethods对应的#34可以找到此处lambda InvokeDynamic指令对应的引导方法是LambdaMetafactory.metafactory,其返还一个CallSite

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(); }

观察源码可得,其通过new一个InnerClassLambdaMetafactory并调用buildCallSite方法创造了类似内部类的lambda CallSite 有兴趣可以看buildCallSite方法的源码 InnerClassLambdaMetafactory类的源码注释是

Lambda metafactory implementation which dynamically creates an inner-class-like class per lambda callsite.

至此,lambda表达式的脱糖过程已经了解完成,其中很多细节设计太多java字节码知识,本汪也不是太了解,有兴趣可以多看看java虚拟机规范

至于为什么要绕一圈生成一个内部类的动态调用点然后执行,而不是直接把lambda编译成内部类,也许是为了减少编译后的文件数,具体不得而知,有待研究


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表