博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Kotlin的一次lambda探险
阅读量:6834 次
发布时间:2019-06-26

本文共 3698 字,大约阅读时间需要 12 分钟。

问题

最近在用kotlin lambda的时候,遇到一个诡异的报错问题

java.lang.NoClassDefFoundError: Failed resolution of: Lcom/dependproject/AnonymousActivity$onCreate$s$1$1;复制代码

类定义找不到导致的异常。神奇了,这是个什么鬼类。我确实没有这个类,先贴上代码开始进行分析

val s = inlineLambda {      object : AnonymousClass
("") { override fun printName() { val context = this@AnonymousActivity } } } abstract class AnonymousClass
{ constructor(t:T) abstract fun printName() } inline fun
T.inlineLambda(block : (T) -> R):R { return block(this) } 复制代码

概括

先观察一下这些代码,发现分别有lambda语法,inline内联函数还有匿名类。


好的,观察好了,并没有发现上面那个奇怪的类。这时候我们就需要借助工具去观察表面看不到的东西,也就是字节码。 使用Android Studio导航栏的Tool的Kotlin工具,把源码转成字节码如下:

L11    LINENUMBER 55 L11    NEW com/dependproject/AnonymousActivity$onCreate$$inlined$inlineLambda$lambda$1    DUP    LDC ""    ALOAD 0    INVOKESPECIAL com/dependproject/AnonymousActivity$onCreate$$inlined$inlineLambda$lambda$1.
(Ljava/lang/Object;Lcom/dependproject/AnonymousActivity;)V L12 LINENUMBER 59 L12 L13 NOP L14 LINENUMBER 54 L14 CHECKCAST com/dependproject/AnonymousActivity$onCreate$s$1$1 ASTORE 2 L15复制代码

咦,发现没,那个诡异的类出现了,

CHECKCAST com/dependproject/AnonymousActivity$onCreate$s$1$1 复制代码

意思就是检查类型,对应上面的代码就是赋值给s的操作之一,赋值之前检查类的类型。那这个类不就是inlineLambda返回的类型吗?我们接着看下去,看inlineLambda方法,这个方法其实很简单,就是返回lambda表达式的类型,lambda表达式的类型其实是上面AnonymousClass匿名类的实现。

那为什么生成的类找不到呢,我们再看到上面的字节码,发现了一个也是奇怪名字的类

com/dependproject/AnonymousActivity$onCreate$$inlined$inlineLambda$lambda$1 复制代码

,迅速过一遍代码,发现这个类就是匿名类生成的。

那就奇怪了,为什么生成的匿名类名字和checkcast名字不一样呢。

分析

以前老师常常教我们,做题要大胆想象,认真推敲。其实分析也一样,大胆想象。观察checkcast的类名,发现名字有迹可循,是类名+方法名+$1$1,即com/dependproject/AnonymousActivity + onCreate + $1$1。那个new的匿名类名字比较长,不过规则一样。即com/dependproject/AnonymousActivity + onCreate + inlined + inlineLambda + 1,这些是编号来的,可以不用管,所以匿名类的名字组成都是有迹可循的。 那为什么名字不一样呢。

接下来就是我的大胆推断了。我认为通过inline内联函数传入的lambda表达式生成的匿名类名字组成规则 = class + method + inlined + method。而外部认为的 类名规则则为 class + method,所以因为编译器不够完善在inline的情况下导致生成的类名规则不统一导致的。

哇, 这解释真牛逼。

乍听之下都是对的。我们来验证一下。

其实上面那个lambda表达式的干扰变量有点多,把方法里面的操作去掉,按照我上面的大胆解释,应该也是要报错的。

然而,啪啪打脸。非但不报错,而且生成的匿名类的名字恰恰是

com/dependproject/AnonymousActivity$onCreate$s$1$1复制代码

这个。对比代码发现差异在于 this@AnonymousActivity这里。没办法,我们回头看看刚刚生成的字节码。

com/dependproject/AnonymousActivity$onCreate$$inlined$inlineLambda$lambda$1复制代码

发现没,有一个putfield其实就是赋值操作,给类的成员变量赋值AnonymousActivity对象。其实看到这里,我还是进行了大胆猜想。加上了指向外部的变量之后,匿名类的编译命名规则会有所改变,而规则就是上面刚刚说的那个规则。但是外部类在执行checkcast的时候,还是按照旧的规则去组装命名。这些纯粹是个人猜想,没有进行验证。

唠叨

对于内联函数,少了方法的调用,性能更加,通过字节码可以发现内联函数 + 匿名函数 + lambda其实会生成一个特定规则命名的类。

但是对于非内联函数,情况又不一样。我们先看到lambda这个方法的调用

lambda {     object : AnonymousClass
("") { override fun printName() { it } } }复制代码

对应的字节码

ALOAD 0    ALOAD 0    GETSTATIC com/dependproject/AnonymousActivity$onCreate$1.INSTANCE : Lcom/dependproject/AnonymousActivity$onCreate$1;    CHECKCAST kotlin/jvm/functions/Function1    INVOKEVIRTUAL com/dependproject/AnonymousActivity.lambda (Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object;    POP复制代码

匿名类的返回字节码如下:

LINENUMBER 20 L1    NEW com/dependproject/AnonymousActivity$onCreate$1$1    DUP    LDC ""    INVOKESPECIAL com/dependproject/AnonymousActivity$onCreate$1$1.
(Ljava/lang/Object;)V复制代码

发现调用lambda会产生两个类,一个是

AnonymousActivity$onCreate$1复制代码

另一个是

AnonymousActivity$onCreate$1$1复制代码

第一个用于lambda表达式的转换, 代码如下:

类继承于kotlin/jvm/internal/Lambda,其中有个方法,想象大家应该都认识,那就是invoke。平时对于lambda的调用有两种方式,

val lambdaFun = {i:Int -> i}    lambdaFun(1)    lambdaFun.invoke(1)复制代码

其中有一种就是invoke,就是在编译过程中产生的。 AnonymousActivity$onCreate$1$1就是返回的匿名类的实现类。

总结

大胆猜想,多多思考。

后话

感谢同事在kotlin论坛看到相关的问题以及讨论,。生成inlined命名的规则在,大家有兴趣可以瞧瞧。值得一提的是,这个bug在14年提出的。

转载地址:http://crqkl.baihongyu.com/

你可能感兴趣的文章
linux系统启动级别
查看>>
bash编程-循环控制的结构
查看>>
Java-第三章-使用if选择结构实现,如果年龄够7岁或5岁并且是男,可以搬桌子
查看>>
使用 /proc 文件系统来访问 Linux 内核的内容
查看>>
andriod之log打印
查看>>
我的友情链接
查看>>
Web应用中的缓存一致性问题
查看>>
通过Android重审GET和POST请求
查看>>
马王堆汉墓帛书‧老子甲本——道经
查看>>
ruby中DBI连接MySQL数据库步骤详解
查看>>
mongodb 的PHP 扩展
查看>>
bp神经网络
查看>>
彻底理解cookie,session,localStorage(附代码)
查看>>
你还记得当初为什么进入IT行业吗?
查看>>
[翻译]MongoDb 架构(MongoDb Architecture)
查看>>
oracle统计数据库所有表的数据记录数SQL
查看>>
随机森林案例分析:德国银行信贷风险分析
查看>>
批量去除歌曲tag标签
查看>>
驰骋工作流引擎设计系列05 启动流程设计
查看>>
Java 启动线程并保持
查看>>