又被 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 抹黑

目录
相关文章
|
4月前
|
XML 数据安全/隐私保护 数据格式
微信xml卡片消息生成器,微信xml卡片生成啊制作工具,卡片消息一键发送【python】
这个实现包含三个主要模块:核心生成器类、预定义模板类和主程序入口。核心类负责获取微信
|
运维 负载均衡 网络协议
从底层技术来看,GSLB 究竟难在哪儿
本文作者吕宏利来自硅谷的SRE,有着多年的国内外大型互联网公司运维开发经验,专注于分布式系统设计、监控、容量规划,数据中心技术以及生产环境的最佳实践。在本文中他将他将向读者介绍什么是GSLB,以及实现细节和维护方法。
8941 0
|
Java 编译器 程序员
"语法糖"
【10月更文挑战第17天】
|
关系型数据库 MySQL Java
Mysql开启ssl加密协议及Java客户端配置操作指南
本文主要讲述的是Mysql开启了ssl协议之后,Java客户端如何正确的链接Mysql访问数据
Mysql开启ssl加密协议及Java客户端配置操作指南
|
机器学习/深度学习 分布式计算 PyTorch
大规模数据集管理:DataLoader在分布式环境中的应用
【8月更文第29天】随着大数据时代的到来,如何高效地处理和利用大规模数据集成为了许多领域面临的关键挑战之一。本文将探讨如何在分布式环境中使用`DataLoader`来优化大规模数据集的管理与加载过程,并通过具体的代码示例展示其实现方法。
820 1
|
Android开发 UED
|
C# Windows
C# 串口关闭时主界面卡死原因分析
串口程序关闭导致界面卡死的原因是主线程与辅助线程间的死锁。问题出在`SerialPort.Close()`方法与`DataReceived`事件处理程序。`DataReceived`事件在`lock (stream)`块中执行,而`Close()`方法会关闭`SerialStream`并锁定自身。当辅助线程处理数据并尝试更新UI时,UI线程因调用`Close()`被阻塞,造成死锁。解决办法是让`DataReceived`事件处理程序使用`this.BeginInvoke()`异步更新界面,避免等待UI线程,从而防止死锁。
|
安全 网络安全 数据安全/隐私保护
iOS App 的打包和上架流程
iOS App 的打包和上架流程
|
消息中间件 存储 Java
Kotlin中正确的使用Handler
如果`Handler`在`Activity`中是以非静态内部类的方式初始化的,那么`Handler`默认就会持有`Activity`的实例,因为在`Java`中:**非静态内部类默认会持有外部类的实例,而静态内部类不会持有外部类的实例**
684 0
|
开发框架 数据可视化 Java
.NET6+Quartz实现定时任务
.NET6+Quartz实现定时任务
520 0