从零开始开发JVM语言(十一)Lambda
2016-06-26 23:23
357 查看
摘要: 编译器 语义分析 Lambda
目录戳这里
这篇说说“内部方法”和“Lambda”如何解析。
先看看java是怎么实现lambda的:
生成如下字节码:
这段很好理解,使用
此外生成了一个private方法:
这个方法就是lambda表达式中的语句:把整数1存储在0位置。
还生成了一个
有点长,不过仔细看下就是调用一个方法罢了。上一篇说过lambda bootstrap方法前三个参数是必须的,其它参数可以自己加。JDK加了三个
很简单吧?
生成的是指定类型对象,并且对象是由
在这个例子中有两点没有体现
对于非static方法中的lambda并不会使用
如果有已定义的局部变量,那么生成的方法会把
所以说,要解析lambda就要生成一个新的方法,这个方法是private的。
在
#内部方法
代码戳这里
所谓的“内部方法”是指在方法中再定义一个方法。例如
在
我的语言
语义分析在遇到内部方法时,将检查是否有重名方法,参数名是否重复等。这些validation就不多说了。
在合法性检查结束后,编译器将在本类中创建一个新方法。这个方法的参数为(之前定义过的局部变量,内部方法定义的变量)
例如上述
在调用时自然也需要将局部变量传入。所以调用看起来是
这么做的好处是:实现非常简单,几乎不用加入任何额外的东西。新建一个方法,把其中的Statement设置为lambda中的statement,然后跑一下语义分析(实现上基本上就是调用一个方法而已),结束。
而且如此实现,对字段的操作毫无影响。
至于命名,的确需要做额外处理。因为生成的内部方法名称一般都会加个后缀,比如
缺点也有。当然,相比java没啥缺点,毕竟java语法糖少,像内部类还得要
#Lambda
代码戳这里
有了“内部方法”后lambda解析就变得非常方便了。static方法可以直接按照java的实现:
创建一个内部方法
使用和java一模一样的方式调用
但是对于非static方法 或者 要实现的方法“是由抽象类定义的”(就是所谓的functional abstract class,定义在前文有写)。
java对于非static方法生成的lambda会多一个对象表示
那么怎么办呢?其实也很简单。做到这一步了,几本整个编译器架构成型了。嫌麻烦的手动做一个AST来实现/继承函数式的类型,然后再交给语义分析器。不嫌麻烦的直接把语义分析的输出格式做出来。
前者有可能在加了特性后出现莫名其妙的问题,后者实现麻烦。我是用的后者,不过前者其实也是可取的,测试用例写好点就行了。
这个“手动”加的类应该有什么特性呢?
需要保持所有的已定义的局部变量。
要知道执行哪个方法。
用lambda中的语句实现那唯一一个没实现的方法。
我构建的类大概长这样
实现中逻辑如下:
创建一个内部方法
获取指向它的MethodHandle
构建List存放局部变量
创建上述的类
用MethodHandle,this,List构造这个类
最终获取到的就是lambda表达式的返回对象了。
#Runtime Lambda
那么怎么做呢?
验证是否为函数式接口/抽象类上一篇题过,运行时的做法更简单,直接
接口的cast比较简单,jdk提供了
而类的实现并没有那么简单,毕竟工程上也得要cglib这种第三方库。
然而我们在运行时有编译器呀~我的做法是最原始的:拼接字符串。把源代码拼出来然后丢给编译器。代码戳这里
这篇就这些吧~下一篇说说“重载方法”执行哪个如何确定~
最后,希望看官能够关注我的编译器哦~Latte
目录戳这里
这篇说说“内部方法”和“Lambda”如何解析。
先看看java是怎么实现lambda的:
public static void x() { Runnable r = () -> { }; }
生成如下字节码:
public static void x(); descriptor: ()V flags: ACC_PUBLIC, ACC_STATIC Code: stack=1, locals=1, args_size=0 0: invokedynamic #53, 0 // InvokeDynamic #0:run:()Ljava/lang/Runnable; 5: astore_0 6: return
这段很好理解,使用
indy指令获取一个Runnable对象,然后保存在0号位置上。
此外生成了一个private方法:
lambda$x$0
private static void lambda$x$0(); descriptor: ()V flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC Code: stack=1, locals=1, args_size=0 0: iconst_1 1: istore_0 2: return
这个方法就是lambda表达式中的语句:把整数1存储在0位置。
还生成了一个
indy bootstrap method
0: #165 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: #166 ()V #167 invokestatic SimpleTest.lambda$x$0:()V #166 ()V
有点长,不过仔细看下就是调用一个方法罢了。上一篇说过lambda bootstrap方法前三个参数是必须的,其它参数可以自己加。JDK加了三个
MethodType。
invokestatic SimpleTest.lambda$x$0:()V表示的是lambda对象指向的方法,在执行lambda时执行的就是
lambda$x$0中的语句。
很简单吧?
生成的是指定类型对象,并且对象是由
indy指令获取的,所以还要规定bootstrap方法之类的东西。而lambda中的过程在某个private方法中规定。
在这个例子中有两点没有体现
对于非static方法中的lambda并不会使用
this,而是额外传入一个对象,用来表示当前对象。不知道为何这么设计。
如果有已定义的局部变量,那么生成的方法会把
用到的局部变量作为参数。
所以说,要解析lambda就要生成一个新的方法,这个方法是private的。
在
Latte中,“内部方法”会生成一个方法,并且会把所有已定义的变量作为方法参数的一部分。所以我在实现时先实现了内部方法才去解析
lambda。
#内部方法
代码戳这里
所谓的“内部方法”是指在方法中再定义一个方法。例如
foo(a) b=1 bar(c) return a+b+c d=2 return bar(3)+d
在
foo中由定义了一个方法
bar。内部方法可以访问外部的局部变量。这似乎有点闭包的意思?
我的语言
Latte支持“内部方法”,但是不支持闭包。因为
Latte的内部方法只能访问外部变量,它们传入方法时新开了引用,所以在内部方法内可以赋值,但是不影响外部。(好吧,其实是实现时偷懒了。后续版本再加入修改外部变量的特性吧。。)而且方法并非变量,不能像js之类的语言那样返回一个函数,所以和闭包没有关系,只是一个方法而已。
语义分析在遇到内部方法时,将检查是否有重名方法,参数名是否重复等。这些validation就不多说了。
在合法性检查结束后,编译器将在本类中创建一个新方法。这个方法的参数为(之前定义过的局部变量,内部方法定义的变量)
例如上述
foo bar的例子,在
bar之前定义了
a,
b两个局部变量(d在bar之后,所以并不会被捕捉(capture)),而
bar自己有一个参数
c,所以最终生成的方法大致是这样:
xxx(a,b,c) return a+b+c
在调用时自然也需要将局部变量传入。所以调用看起来是
bar(3),实际上编译器会编译成
bar(a,b,3)。
这么做的好处是:实现非常简单,几乎不用加入任何额外的东西。新建一个方法,把其中的Statement设置为lambda中的statement,然后跑一下语义分析(实现上基本上就是调用一个方法而已),结束。
而且如此实现,对字段的操作毫无影响。
static方法没有
this,非static方法的
this也可以直接用(因为编译器才不管你是不是内部方法呢~)。所以我强烈推荐这种实现方式。
至于命名,的确需要做额外处理。因为生成的内部方法名称一般都会加个后缀,比如
$0,
$1这样的。于是需要做一个映射:内部方法名到实际方法的映射。
缺点也有。当然,相比java没啥缺点,毕竟java语法糖少,像内部类还得要
final才能capture。
#Lambda
代码戳这里
有了“内部方法”后lambda解析就变得非常方便了。static方法可以直接按照java的实现:
创建一个内部方法
使用和java一模一样的方式调用
indy和
bootstrap method来获取对象
但是对于非static方法 或者 要实现的方法“是由抽象类定义的”(就是所谓的functional abstract class,定义在前文有写)。
java对于非static方法生成的lambda会多一个对象表示
this,而不会直接用
this,与我的“内部方法”生成的方法策略不符。而java与支持“lambda抽象类”,所以也不能直接用。
那么怎么办呢?其实也很简单。做到这一步了,几本整个编译器架构成型了。嫌麻烦的手动做一个AST来实现/继承函数式的类型,然后再交给语义分析器。不嫌麻烦的直接把语义分析的输出格式做出来。
前者有可能在加了特性后出现莫名其妙的问题,后者实现麻烦。我是用的后者,不过前者其实也是可取的,测试用例写好点就行了。
这个“手动”加的类应该有什么特性呢?
需要保持所有的已定义的局部变量。
要知道执行哪个方法。
用lambda中的语句实现那唯一一个没实现的方法。
我构建的类大概长这样
class LambdaClassName( MethodHandle 要执行的方法引用 Object 在哪个对象上执行 List 局部变量 ):要实现的类型 实现的方法(参数):返回类型 ; (当然,也有可能是void,那样就执行方法但不返回其值) newList=LinkedList(local); newList.add(0, o); newList.add(x); newList.add(y); return methodHandle.invokeWithArguments(newList);
实现中逻辑如下:
创建一个内部方法
获取指向它的MethodHandle
构建List存放局部变量
创建上述的类
用MethodHandle,this,List构造这个类
最终获取到的就是lambda表达式的返回对象了。
#Runtime Lambda
Latte可以不指定类型,所以默认lambda类型为
lt::lang::function::FunctionX,所以可能需要在运行时cast到所需类型。
那么怎么做呢?
验证是否为函数式接口/抽象类上一篇题过,运行时的做法更简单,直接
getMethods(),看abstract方法个数就行。
接口的cast比较简单,jdk提供了
Proxy
而类的实现并没有那么简单,毕竟工程上也得要cglib这种第三方库。
然而我们在运行时有编译器呀~我的做法是最原始的:拼接字符串。把源代码拼出来然后丢给编译器。代码戳这里
这篇就这些吧~下一篇说说“重载方法”执行哪个如何确定~
最后,希望看官能够关注我的编译器哦~Latte
相关文章推荐
- 浅谈汇编器、编译器和解释器
- 让我们做个简单的解释器(三)
- 让我们做个简单的解释器(一)
- 用 350 行代码从零开始,将 Lisp 编译成 JavaScript
- 基于JSP编译器基本语法的使用详解
- C#命令行编译器配置方法
- Java虚拟机JVM性能优化(二):编译器
- AngularJS HTML编译器介绍
- 实现接口时@Override注解问题
- 条款06:若不想使用编译器自动生成的函数,就该明确拒绝
- g++编译 参数 .
- 关于 ndk和jni的区别
- vim中的杀手级插件: YouCompleteMe
- Google C++ unit test 在ARM Android 2.3 上的编译与使用
- 从代码示例了解ECMAScript5新特性
- Java的可移植性受到广泛使用
- C++ .H .CPP
- Windows Server 2003远程桌面多用户连接问题
- centos下安装nginx