又被 Kotlin 语法糖坑惨的一天

简介: 又被 Kotlin 语法糖坑惨的一天

源起是同事的一次反馈,在提测期间报了一个 Kotlin.Lazy 的空指针异常,Lazy 的定义如下:


class TestA{
    ...
    val xxxx:Service? by lazy{
         xxxService()   
    }
    ...
}
复制代码


看起来很平常的 by lazy 为何会报空指针?在深入 lazy 源码查看的时候,并未发现任何可疑点,由于当时的代码逻辑涉及到并发调用,也查看了 by lazy  的初始化,默认实现是 SynchronizedLazyImpl,已经做了线程安全操作。


为了避免太多代码的干扰,我们将涉及到 by lazy 使用的地方都拷贝到了一个 Test 类中,然后通过 Decompile 反编译成 Java 代码来查看是否是 kotlin 的问题。


Kotlin 代码如下:


class TestA {
    init {
        ....
        initView()
    }
    private fun initView() {
        // 调用 Service 方法
        service?.getName()
    }
    private val service: AService? by lazy {
        AService()
    }
}
复制代码


反编译后的 Java 代码:


public final class TestA {
   private final Lazy service$delegate;
   private final void initView() {
      // 1、获取 service 实例
      AService var10000 = this.getService();
      if (var10000 != null) {
         var10000.getName();
      }
   }
   private final AService getService() {
      Lazy var1 = this.service$delegate;
      Object var3 = null;
      // 2、调用 Lazy 的 getValue 方法
     .return (AService)var1.getValue();
   }
   public TestA() {
      this.initView();
      // 3、初始化 Lazy 实例
     .this.service$delegate = LazyKt.lazy((Function0)null.INSTANCE);
   }
}
复制代码


通过代码的反编译立马查到问题:


  1. 在 TestA 的构造方法中,先执行  initView 方法获取 AService 的实例
  2. 但 getService 方法中的  Lazy 还没有初始化,却直接调用了 getValue 方法触发空指针异常
  3. 在 initView 结束之后再做 Lazy 的初始化,这时候已经晚了,异常已经出现了


那如何解决这问题呢?只需将 by lazy 提到了 init 代码块的前面,如下:


class TestA {
    private val service: AService? by lazy {
        AService()
    }
    init {
        initView()
    }
    ...
}
复制代码


反编译结果:


public final class TestA {
   private final Lazy service$delegate;
   ....
   public TestA() {
      // 1、初始化 Lazy 实例
     .this.service$delegate = LazyKt.lazy((Function0)null.INSTANCE);
      // 2、再调用 getService 方法
     .this.initView();
   }
}
复制代码


  1. 构造终于是先初始化 Lazy 对象
  2. 再调用 initView 方法,这时候方法内的  Lazy.getValue 就能被正常调用了

是不是有点违背常识?为什么在方法里调用一个变量还会涉及到变量放置的位置,Kotlin 这高级语法糖恐怕连 C 都不如吧(嘲笑一番,哈哈)。


那 Kotlin 真的没有对其做语法检查吗?其实是有的,我改变下代码给大家看下:


image.png

IDE 会提示当前 service 未初始化,但该提示仅限在 init 代码块中调用 lazy 的时候提示,如果在 init 中调用一个中间方法,然后再从中间方法调用 lazy,该提示校验将会失效


又被 Kotlin 语法糖坑惨的一天!!!


看到大家在公众号的反馈,我觉得有必要再总结一下:


1、kotlin 并没有规定 init 代码块不能调用 lazy,并且 lazy 与 init 本身就没冲突

2、在 init 代码块中直接调用 lazy ,Kt 会检测变量是否已被初始化,从这个点可以看出,Kt 团队是有意识到这个问题的,但可惜的是,在深度调用上,该检查失效了,导致问题被带到了运行时。Kt 团队为什么不能将检测做的更智能呢?

3、很多人对变量的申明都会放在方法上面,所以,基本上也不会遇到这个 case,从而认为这是基操。其实,基操这词太带有主观意识了,大家都知道代码中不能这么写,那为什么还要开发出 lint 工具呢?总不能靠基操来规范代码吧。

文章主要是介绍使用 Kt 时遇到的坑,项目中我们也在积极用 Kt,那些抹黑 Kt 的论调可以歇歇了,不带有论据的鼓吹才是真的给 Kt 抹黑

目录
相关文章
|
1月前
|
设计模式 安全 Java
Kotlin
Kotlin
17 0
|
3月前
|
人工智能 Dart Java
Kotlin基础语法
Kotlin基础语法
|
8月前
|
Java Kotlin
Kotlin中函数式编程的详解
Kotlin中函数式编程的详解
61 0
|
Java 开发者 Kotlin
Kotlin中lambda表达式详解
lambda运算时java后面版本引进的,所以实现的仅仅是从形式上简化代码,内部的优化并不是非常出色,而Kotlin一开始就支持函数式编程,使得其lambda表达式具有性能上的优势,同时Kotlin简洁的风格也给lambda表达式进一步简化提供了一个良好的实现方式,下面带大家具体看看他们之间的区别以及如何更好的使用Kotlin的极简化lambda表达式
134 0
Kotlin中lambda表达式详解
|
Java 编译器 Kotlin
聊聊Kotlin中的元编程
聊聊Kotlin中的元编程
273 0
聊聊Kotlin中的元编程
|
JavaScript 前端开发 Java
浅谈Kotlin中的函数
本文简单谈下Kotlin中的函数,包括表达式函数体,命名参数,默认参数,顶层函数,扩展函数,局部函数,Lambda表达式,成员引用,with/apply函数等。从例子入手,从一般写法到使用特性进行简化,再到原理解析。
1111 0
分享Kotlin的一个小技巧
分享Kotlin的一个小技巧
|
SQL 前端开发 JavaScript
Kotlin的分享
在公司部门小组中做了一次kotlin的分享 # kotlin简介 Kotlin是由JetBrains公司(IDEA开发者)所开发的编程语言,其名称来自于开发团队附近的科特林岛。 多平台开发 JVM :Android; Server-Side Javascript:前端 Native(beta) :开发原生应用 windows、macos、linux Swift与Kotli
1503 0
Kotlin可控性探索
目录介绍 01.可空性 02.安全调用运算符:?. 03.Elvis运算符:?: 04.安全转换运算符:as? 05.非空断言:!! 06.let函数说明 07.可空类型的扩展 08.Java中判断方式 09.
1287 0
|
Java Kotlin 编译器
Kotlin 基础语法
Kotlin 文件以 .kt 为后缀。 包声明 包的声明应处于源文件顶部: package my.demo import java.util.* // …… kotlin源文件不需要相匹配的目录和包,源文件可以放在任何文件目录。
1022 0