【问题标题】:Two Additional types in default constructor in Kotlin?Kotlin 默认构造函数中的两种附加类型?
【发布时间】:2018-12-24 10:15:56
【问题描述】:

由于我一直在使用 kotlin-reflect 来调用我的默认值并声明了一个,因此我看到了第二个不同的构造函数。

我意识到我的构造函数中添加了两个不同的字段 int arg3kotlin.jvm.internal.DefaultConstructorMarker arg4

data class Model(
    @SerializedName("xyz") val entity: String?,
    @SerializedName("abc") val id: Long? = null
)
val constructors = clazz.declaredConstructors // how I call the constructors

我的实际问题是为什么我有这两个字段,它背后的逻辑是什么?

提前致谢。

【问题讨论】:

    标签: android kotlin kotlin-reflect


    【解决方案1】:

    这两个参数被添加到 Kotlin 编译器为所有具有默认参数的函数和构造函数生成的特殊合成成员中。

    使用 Java 反射,您可以通过检查 isSynthetic() 并找到不是的来过滤掉这些合成函数和构造函数。

    整数参数是位掩码。当从 Kotlin 调用这样的函数时,会生成一个位掩码并作为参数传递。这些位显示函数的哪些默认参数被传递显式参数,哪些应该使用默认值。

    DefaultConstructorMarker 参数用于确保合成构造函数(接受位掩码)不会与具有相同参数签名的不同构造函数发生冲突,最后是 Int。传递给标记参数的参数不以任何方式使用,始终为null

    实际上,每个函数或构造函数分别生成两个方法或构造函数,它们至少有一个默认参数:一个与声明的签名相同且没有附加参数,另一个也接受位掩码和标记。

    如果你检查这样一个函数的字节码,你会发现一个函数声明大致如下:

    fun foo(bar: String, baz: List<String> = emptyList(), qux: Set<String> = emptySet()) = 0
    

    字节码中真正的方法是:

    // access flags 0x19
    // signature (Ljava/lang/String;Ljava/util/List<Ljava/lang/String;>;Ljava/util/Set<Ljava/lang/String;>;)I
    // declaration: int foo(java.lang.String, java.util.List<java.lang.String>, java.util.Set<java.lang.String>)
    public final static foo(
        Ljava/lang/String;
        Ljava/util/List;
        Ljava/util/Set;
    )I
      // annotable parameter count: 3 (visible)
      // annotable parameter count: 3 (invisible)
      @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
      @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 1
      @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 2
    L0
      ALOAD 0
      LDC "bar"
      INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull (Ljava/lang/Object;Ljava/lang/String;)V
      ALOAD 1
      LDC "baz"
      INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull (Ljava/lang/Object;Ljava/lang/String;)V
      ALOAD 2
      LDC "qux"
      INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull (Ljava/lang/Object;Ljava/lang/String;)V
    L1
      LINENUMBER 16 L1
      ICONST_0
      IRETURN
    L2
      LOCALVARIABLE bar Ljava/lang/String; L0 L2 0
      LOCALVARIABLE baz Ljava/util/List; L0 L2 1
      LOCALVARIABLE qux Ljava/util/Set; L0 L2 2
      MAXSTACK = 2
      MAXLOCALS = 3
    

    而生成的处理位掩码并在必要时计算默认值的包装器是一个单独的方法:

    // access flags 0x1009
    public static synthetic foo$default(
        Ljava/lang/String;
        Ljava/util/List;
        Ljava/util/Set;
        I
        Ljava/lang/Object;
    )I
      ILOAD 3
      ICONST_2
      IAND
      IFEQ L0
    L1
      LINENUMBER 16 L1
      INVOKESTATIC kotlin/collections/CollectionsKt.emptyList ()Ljava/util/List;
      ASTORE 1
    L0
      ILOAD 3
      ICONST_4
      IAND
      IFEQ L2
      INVOKESTATIC kotlin/collections/SetsKt.emptySet ()Ljava/util/Set;
      ASTORE 2
    L2
      ALOAD 0
      ALOAD 1
      ALOAD 2
      INVOKESTATIC FooKt.foo (Ljava/lang/String;Ljava/util/List;Ljava/util/Set;)I
      IRETURN
      MAXSTACK = 3
      MAXLOCALS = 5
    

    注意后者如何检查位掩码(使用ILOAD 3ICONST_xIAND)然后有条件地(当IFEQ Lx 不跳过它时)评估默认参数。

    构造函数与普通函数的不同之处在于,它们的名称中不能有后缀$default,因此需要标记以避免可能的签名冲突。

    【讨论】:

    • UPD:添加了一种查找真实构造函数而不是合成构造函数的方法。
    猜你喜欢
    • 2018-04-23
    • 2019-01-03
    • 2017-04-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多