又被 Kotlin 语法糖坑惨的一天

2022-11-30 14:40:31 浏览数 (1)

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

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

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

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

Kotlin 代码如下:

代码语言:javascript复制
class TestA {

    init {
        ....
        initView()
    }

    private fun initView() {
        // 调用 Service 方法
        service?.getName()
    }

    private val service: AService? by lazy {
        AService()
    }
}

反编译后的 Java 代码:

代码语言:javascript复制
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 代码块的前面,如下:

代码语言:javascript复制
class TestA {
    private val service: AService? by lazy {
        AService()
    }

    init {
        initView()
    }
    ...
}

反编译结果:

代码语言:javascript复制
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 真的没有对其做语法检查吗?其实是有的,我改变下代码给大家看下:

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

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

0 人点赞