NoArg 插件的 invokeInitializers 配置

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

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

 
   
  1. @Poko

  2. data class DontDoThis(val requiredProperty: String,

  3.                      val invalidDefaultValue: Int = 1000) {

  4.    val wontBeInitialized by lazy {

  5.        "HelloWorld"

  6.    }

  7.    val wontBeInitialized2 = 2

  8. }

wontBeInitializedwontBeInitialized2 都不会被正常初始化。

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

  • 而后者,本身就是一个整型,不被初始化,访问时就是默认的 0。

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

  • 小心,在数据类当中用 Lazy 要谨慎!

  • 还在被数据类的序列化折磨?是时候丢弃 Gson 了

  • 数据类增加nonNull字段反序列化的坑

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

 
   
  1. noArg{

  2.    invokeInitializers = true

  3.    annotation "com.bennyhuo.annos.Poko"

  4. }

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

 
   
  1. public DontDoThis(){

  2.    super();

  3.    wontBeInitialized$Delegate = new .... ;

  4.    wontBeInitialized2 = 2;

  5. }

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

 
   
  1. public DontDoThis(){

  2.    super();

  3. }

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

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

 
   
  1. public <init>()V

  2.  L0

  3.    ALOAD 0

  4.    INVOKESPECIAL java/lang/Object.<init> ()V

  5.    RETURN

  6.  L1

  7.    LOCALVARIABLE this Lkotlin/Unit; L0 L1 0

  8.    MAXSTACK = 1

  9.    MAXLOCALS = 1

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

 
   
  1. // access flags 0x1

  2. public <init>()V

  3.   L0

  4.    ALOAD 0

  5.    INVOKESPECIAL java/lang/Object.<init> ()V

  6.   L1

  7.    LINENUMBER 6 L1

  8.    ALOAD 0

  9.    GETSTATIC com/bennyhuo/DontDoThis$wontBeInitialized$2.INSTANCE : Lcom/bennyhuo/DontDoThis$wontBeInitialized$2;

  10.    CHECKCAST kotlin/jvm/functions/Function0

  11.    INVOKESTATIC kotlin/LazyKt.lazy (Lkotlin/jvm/functions/Function0;)Lkotlin/Lazy;

  12.    PUTFIELD com/bennyhuo/DontDoThis.wontBeInitialized$delegate : Lkotlin/Lazy;

  13.   L2

  14.    LINENUMBER 10 L2

  15.    ALOAD 0

  16.    ICONST_2

  17.    PUTFIELD com/bennyhuo/DontDoThis.wontBeInitialized2 : I

  18.    RETURN

  19.   L3

  20.    LOCALVARIABLE this Lkotlin/Unit; L0 L3 0

  21.    MAXSTACK = 2

  22.    MAXLOCALS = 1

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

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


转载请注明出处:微信公众号 Kotlin

NoArg 插件的 invokeInitializers 配置_第1张图片

你可能感兴趣的:(NoArg 插件的 invokeInitializers 配置)