NoArg 插件的 invokeInitializers 配置

2020-02-20 13:31:47 浏览数 (1)

上周的时候 Kotlin 1.3 发布了,由于之前 RC 版本以及 KotlinConf 造势很多,所以正式版出来之后大家就改个版本号,把协程的 experimental 去掉,就差不多了。

上周微信群里有小伙伴说到 NoArg 插件生成的无参构造方法不会初始化类内部定义的属性,例如:

代码语言:javascript复制
@Poko
data class DontDoThis(val requiredProperty: String,
                      val invalidDefaultValue: Int = 1000) {
    val wontBeInitialized by lazy {
        "HelloWorld"
    }

    val wontBeInitialized2 = 2
}

wontBeInitializedwontBeInitialized2 都不会被正常初始化。

  • 对于前者,我们知道编译器要为我们生成一个代理对象,我们访问它时,实际上是访问代理对象来获取对应的值,而代理对象因没有被初始化,导致访问前者时会出空指针。
  • 而后者,本身就是一个整型,不被初始化,访问时就是默认的 0。

前面我们已经有文章提到过这个现象,包括对于 Gson 反序列化数据类的时候出现的种种问题中,也有些与此有关。可以参考相关文章:

  • 小心,在数据类当中用 Lazy 要谨慎!
  • 还在被数据类的序列化折磨?是时候丢弃 Gson 了
  • 数据类增加nonNull字段反序列化的坑

其实 NoArg 的配置还有一个叫 invokeInitializers 的家伙,你可以这么配置:

代码语言:javascript复制
noArg{
    invokeInitializers = true
    annotation "com.bennyhuo.annos.Poko"
}

它是什么意思呢?对于前面的那个类,这个配置为 true 之后,生成的无参构造器就会大致相当于:

代码语言:javascript复制
public DontDoThis(){
    super();
    wontBeInitialized$Delegate = new .... ;
    wontBeInitialized2 = 2;
}

而不加这个配置的话,就是这样:

代码语言:javascript复制
public DontDoThis(){
    super();
}

看来这个配置还是很有用的。

过去我之前好几次看到它,并尝试配置,结果用 IntelliJ Kotlin 插件自带的 "Show Kotlin Bytecode" 看了之后,发现生成的构造器没有任何变化:

代码语言:javascript复制
public <init>()V
  L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
    RETURN
  L1
    LOCALVARIABLE this Lkotlin/Unit; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1

这让人相当的困惑,我一直以为这个配置没啥用。前几天在看 NoArg 插件的源码时看到这个配置,试了下还是有用的,估计是 "Show Kotlin Bytecode" 没有根据 Gradle 当中的配置来编译导致的吧,大家不用在意了,我们可以在 IntelliJ 里面下载其他看字节码的插件,其实是可以看到

代码语言:javascript复制
// access flags 0x1
public <init>()V
   L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
   L1
    LINENUMBER 6 L1
    ALOAD 0
    GETSTATIC com/bennyhuo/DontDoThis$wontBeInitialized$2.INSTANCE : Lcom/bennyhuo/DontDoThis$wontBeInitialized$2;
    CHECKCAST kotlin/jvm/functions/Function0
    INVOKESTATIC kotlin/LazyKt.lazy (Lkotlin/jvm/functions/Function0;)Lkotlin/Lazy;
    PUTFIELD com/bennyhuo/DontDoThis.wontBeInitialized$delegate : Lkotlin/Lazy;
   L2
    LINENUMBER 10 L2
    ALOAD 0
    ICONST_2
    PUTFIELD com/bennyhuo/DontDoThis.wontBeInitialized2 : I
    RETURN
   L3
    LOCALVARIABLE this Lkotlin/Unit; L0 L3 0
    MAXSTACK = 2
    MAXLOCALS = 1

我们通过 Bytecode Viewer 的功能看到生成的无参构造方法的字节码其实是这样的,其中明显有对类内部的属性初始化的操作。

既然这个配置这么有用,为什么 Kotlin 官方把它默认关闭了?大约是因为 1.1.3 这个版本刚刚带上这个功能,当时因为有一些小问题,大家抱怨升级之后导致代码无法编译通过,影响太大,后来尽管问题已经在 1.1.3-2 修复,但这个可能影响程序结果的配置还是关掉了,这也是为了稳定性考虑,如果大家有明确的需要,还是自己手动打开吧。


0 人点赞