java8 lambda表达式原理
来源:互联网 发布:python ipython 编辑:程序博客网 时间:2024/05/20 01:38
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对比
- 对比 code
public 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编译成内部类,也许是为了减少编译后的文件数,具体不得而知,有待研究
- java8 lambda表达式原理
- java8 lambda表达式原理
- Java8 Lambda表达式教程
- Java8 Lambda表达式教程
- Java8: Lambda表达式语法
- java8 Lambda表达式
- Java8 Lambda表达式教程
- Java8 Lambda表达式
- java8 Lambda表达式
- Java8 Lambda表达式教程
- Java8 Lambda表达式教程
- Java8 Lambda表达式教程
- java8 lambda表达式-语法
- java8 lambda表达式-其他
- java8 lambda表达式
- Java8 Lambda表达式入门
- Spark/Java8 lambda表达式
- Java8 Lambda表达式教程
- 实例展示elasticsearch集群生态,分片以及水平扩展
- Netty学习总结(6)——Netty使用注意事项
- Linux ssh exit,启动的后台进程不会停止
- kaggle Titanic泰坦尼克
- C#批量写入MySQL100w条数据
- java8 lambda表达式原理
- 利用poi完成导出excel功能
- ubuntu Git升级
- TabLayout的使用
- 【选择排序】
- 产品需求文档分享:「病利贴」
- struts2架构中核心对象的探索—— Dispatcher&ConfigurationProvider
- vue2 + router + vuex + vux + axios 开发的一点总计
- redis在工程中的使用