Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Two Additional types in default constructor in Kotlin?

Since I've been using kotlin-reflect to invoke my default and declared one, I see second different constructor.

I have realized that two different fields int arg3 and kotlin.jvm.internal.DefaultConstructorMarker arg4 added to my constructor.

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

My actual question is why I have these 2 fields and what is the logic behind it ?

Thanks in advance.

like image 893
hcknl Avatar asked Dec 24 '18 10:12

hcknl


People also ask

What are the two types of constructors in Kotlin?

In Kotlin, there are two constructors: Primary constructor - concise way to initialize a class. Secondary constructor - allows you to put additional initialization logic.

What is default constructor in Kotlin?

A constructor is a special member function that is invoked when an object of the class is created primarily to initialize variables or properties. A class needs to have a constructor and if we do not declare a constructor, then the compiler generates a default constructor.


1 Answers

These two parameters are added to special synthetic members generated by the Kotlin compiler for all functions and constructors with default parameters.

With Java reflection, you can filter out these synthetic functions and constructors by checking isSynthetic() and finding the one that is not.

The integer parameter is a bit mask. When such a function is called from Kotlin, a bit mask is generated and passed as the argument. The bits show which of the default parameters of the function are passed explicit arguments and which should use the default value.

The DefaultConstructorMarker parameter is used to ensure there's no collision of the synthetic constructor (accepting the bit mask) with a different constructor which has a signature with those same arguments and an Int in the end. The argument passed to the marker parameter is not used in any way, and it is always null.

In fact, there are two methods or constructors generated for each function or constructor, respectively, that has at least one default parameter: one with the same signature as declared and no additional parameters, and the other also accepting a bit mask and the marker.

If you inspect the bytecode of such a function, you will find roughly the following, for a function declaration:

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

The real method in the bytecode is:

// 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

And the generated wrapper that handles the bit mask and calculates the default values if necessary is a separate method:

// 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

Note how the latter checks the bit mask (with ILOAD 3, ICONST_x, IAND) and then conditionally (when IFEQ Lx doesn't skip it) evaluates the default arguments.

Constructors are different from ordinary functions in that they can't have a suffix $default in the name, so the marker is necessary to avoid possible signatures conflict.

like image 192
hotkey Avatar answered Oct 28 '22 15:10

hotkey