I canāt explain the generated java code. I would suggest that you take a look at the generated bytecode.
Full Bytecode
public final class Test/TestKt {
// access flags 0x19
public final static main([Ljava/lang/String;)V
@Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
L0
ALOAD 0
LDC "args"
INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull (Ljava/lang/Object;Ljava/lang/String;)V
L1
LINENUMBER 5 L1
GETSTATIC Test/TestKt.printKotlin : Lkotlin/jvm/functions/Function0;
INVOKEINTERFACE kotlin/jvm/functions/Function0.invoke ()Ljava/lang/Object;
POP
L2
LINENUMBER 6 L2
RETURN
L3
LOCALVARIABLE args [Ljava/lang/String; L0 L3 0
MAXSTACK = 2
MAXLOCALS = 1
// access flags 0x1A
// signature Lkotlin/jvm/functions/Function0<Lkotlin/Unit;>;
// declaration: kotlin.jvm.functions.Function0<kotlin.Unit>
private final static Lkotlin/jvm/functions/Function0; printKotlin
@Lorg/jetbrains/annotations/NotNull;() // invisible
// access flags 0x19
// signature ()Lkotlin/jvm/functions/Function0<Lkotlin/Unit;>;
// declaration: kotlin.jvm.functions.Function0<kotlin.Unit> getPrintKotlin()
public final static getPrintKotlin()Lkotlin/jvm/functions/Function0;
@Lorg/jetbrains/annotations/NotNull;() // invisible
L0
LINENUMBER 8 L0
GETSTATIC Test/TestKt.printKotlin : Lkotlin/jvm/functions/Function0;
ARETURN
L1
MAXSTACK = 1
MAXLOCALS = 0
// access flags 0x8
static <clinit>()V
L0
LINENUMBER 8 L0
GETSTATIC Test/TestKt$printKotlin$1.INSTANCE : LTest/TestKt$printKotlin$1;
CHECKCAST kotlin/jvm/functions/Function0
PUTSTATIC Test/TestKt.printKotlin : Lkotlin/jvm/functions/Function0;
RETURN
MAXSTACK = 1
MAXLOCALS = 0
@Lkotlin/Metadata;(mv={1, 1, 13}, bv={1, 0, 3}, k=2, d1={"\u0000\u001a\n\u0000\n\u0002\u0018\u0002\n\u0002\u0010\u0002\n\u0002\u0008\u0004\n\u0002\u0010\u0011\n\u0002\u0010\u000e\n\u0002\u0008\u0002\u001a\u0019\u0010\u0005\u001a\u00020\u00022\u000c\u0010\u0006\u001a\u0008\u0012\u0004\u0012\u00020\u00080\u0007\u00a2\u0006\u0002\u0010\u0009\"\u0017\u0010\u0000\u001a\u0008\u0012\u0004\u0012\u00020\u00020\u0001\u00a2\u0006\u0008\n\u0000\u001a\u0004\u0008\u0003\u0010\u0004\u00a8\u0006\n"}, d2={"printKotlin", "Lkotlin/Function0;", "", "getPrintKotlin", "()Lkotlin/jvm/functions/Function0;", "main", "args", "", "", "([Ljava/lang/String;)V", "Test"})
// access flags 0x18
final static INNERCLASS Test/TestKt$printKotlin$1 null null
// compiled from: Test.kt
}
// ================Test/TestKt$printKotlin$1.class =================
// class version 50.0 (50)
// access flags 0x30
// signature Lkotlin/jvm/internal/Lambda;Lkotlin/jvm/functions/Function0<Lkotlin/Unit;>;
// declaration: Test/TestKt$printKotlin$1 extends kotlin.jvm.internal.Lambda implements kotlin.jvm.functions.Function0<kotlin.Unit>
final class Test/TestKt$printKotlin$1 extends kotlin/jvm/internal/Lambda implements kotlin/jvm/functions/Function0 {
// access flags 0x1041
public synthetic bridge invoke()Ljava/lang/Object;
ALOAD 0
INVOKEVIRTUAL Test/TestKt$printKotlin$1.invoke ()V
GETSTATIC kotlin/Unit.INSTANCE : Lkotlin/Unit;
ARETURN
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x11
public final invoke()V
L0
LINENUMBER 9 L0
LDC "Hello Kotlin"
ASTORE 1
L1
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
ALOAD 1
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V
L2
L3
LINENUMBER 10 L3
RETURN
L4
LOCALVARIABLE this LTest/TestKt$printKotlin$1; L0 L4 0
MAXSTACK = 2
MAXLOCALS = 2
// access flags 0x0
<init>()V
ALOAD 0
ICONST_0
INVOKESPECIAL kotlin/jvm/internal/Lambda.<init> (I)V
RETURN
MAXSTACK = 2
MAXLOCALS = 1
// access flags 0x19
public final static LTest/TestKt$printKotlin$1; INSTANCE
// access flags 0x8
static <clinit>()V
NEW Test/TestKt$printKotlin$1
DUP
INVOKESPECIAL Test/TestKt$printKotlin$1.<init> ()V
PUTSTATIC Test/TestKt$printKotlin$1.INSTANCE : LTest/TestKt$printKotlin$1;
RETURN
MAXSTACK = 2
MAXLOCALS = 0
@Lkotlin/Metadata;(mv={1, 1, 13}, bv={1, 0, 3}, k=3, d1={"\u0000\u0008\n\u0000\n\u0002\u0010\u0002\n\u0000\u0010\u0000\u001a\u00020\u0001H\n\u00a2\u0006\u0002\u0008\u0002"}, d2={"<no name provided>", "", "invoke"})
OUTERCLASS Test/TestKt null
// access flags 0x18
final static INNERCLASS Test/TestKt$printKotlin$1 null null
// compiled from: Test.kt
// debug info: SMAP
}
Let me try to explain the relevant parts.
/*val printKotlin = */ fun() {
println("Hello Kotlin")
}
generates this part of the bytecode:
// ================Test/TestKt$printKotlin$1.class =================
// class version 50.0 (50)
// access flags 0x30
// signature Lkotlin/jvm/internal/Lambda;Lkotlin/jvm/functions/Function0<Lkotlin/Unit;>;
// declaration: Test/TestKt$printKotlin$1 extends kotlin.jvm.internal.Lambda implements kotlin.jvm.functions.Function0<kotlin.Unit>
final class Test/TestKt$printKotlin$1 extends kotlin/jvm/internal/Lambda implements kotlin/jvm/functions/Function0 {
// access flags 0x1041
public synthetic bridge invoke()Ljava/lang/Object;
ALOAD 0
INVOKEVIRTUAL Test/TestKt$printKotlin$1.invoke ()V
GETSTATIC kotlin/Unit.INSTANCE : Lkotlin/Unit;
ARETURN
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x11
public final invoke()V
L0
LINENUMBER 9 L0
LDC "Hello Kotlin"
ASTORE 1
L1
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
ALOAD 1
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V
L2
L3
LINENUMBER 10 L3
RETURN
L4
LOCALVARIABLE this LTest/TestKt$printKotlin$1; L0 L4 0
MAXSTACK = 2
MAXLOCALS = 2
// access flags 0x0
<init>()V
ALOAD 0
ICONST_0
INVOKESPECIAL kotlin/jvm/internal/Lambda.<init> (I)V
RETURN
MAXSTACK = 2
MAXLOCALS = 1
// access flags 0x19
public final static LTest/TestKt$printKotlin$1; INSTANCE
// access flags 0x8
static <clinit>()V
NEW Test/TestKt$printKotlin$1
DUP
INVOKESPECIAL Test/TestKt$printKotlin$1.<init> ()V
PUTSTATIC Test/TestKt$printKotlin$1.INSTANCE : LTest/TestKt$printKotlin$1;
RETURN
MAXSTACK = 2
MAXLOCALS = 0
@Lkotlin/Metadata;(mv={1, 1, 13}, bv={1, 0, 3}, k=3, d1={"\u0000\u0008\n\u0000\n\u0002\u0010\u0002\n\u0000\u0010\u0000\u001a\u00020\u0001H\n\u00a2\u0006\u0002\u0008\u0002"}, d2={"<no name provided>", "", "invoke"})
The function getās transformed into a class, that way you can pass around instances of this Function (Kotlin treats all higher order functions this way). The code println("Hello Kotlin")
can be found in this class invoke
function.
Also of note is the final static INSTANCE
field which getās set in the static constructor to a new instance of this class.
The assignment of the function to val printKotlin = ...
generates this code:
// access flags 0x1A
// signature Lkotlin/jvm/functions/Function0<Lkotlin/Unit;>;
// declaration: kotlin.jvm.functions.Function0<kotlin.Unit>
private final static Lkotlin/jvm/functions/Function0; printKotlin
@Lorg/jetbrains/annotations/NotNull;() // invisible
// access flags 0x19
// signature ()Lkotlin/jvm/functions/Function0<Lkotlin/Unit;>;
// declaration: kotlin.jvm.functions.Function0<kotlin.Unit> getPrintKotlin()
public final static getPrintKotlin()Lkotlin/jvm/functions/Function0;
@Lorg/jetbrains/annotations/NotNull;() // invisible
L0
LINENUMBER 8 L0
GETSTATIC Test/Test.printKotlin : Lkotlin/jvm/functions/Function0;
ARETURN
L1
MAXSTACK = 1
MAXLOCALS = 0
// access flags 0x8
static <clinit>()V
L0
LINENUMBER 8 L0
GETSTATIC Test/Test$printKotlin$1.INSTANCE : LTest/Test$printKotlin$1;
CHECKCAST kotlin/jvm/functions/Function0
PUTSTATIC Test/Test.printKotlin : Lkotlin/jvm/functions/Function0;
RETURN
MAXSTACK = 1
MAXLOCALS = 0
There are 3 parts to it. First it creates a private static
field of type Function0
to store the reference to the class generated. Then it generates a getter, just like you are used for any property. The last part is in the static constructor. It takes a reference to printKotlin$1.INSTANCE
and stores it in our private field.
So where does the null.INSTANCE
come from? I guess that the java decompiler does not recognize the hidden class TestKt$printKotlin$1
and therefor uses null
instead.
Another fact to note is what happens if you capture values inside of the higher order function. That way the compiler can no longer use the same instance of the function-class so it will create multiple instances, each saving the captured values.
Also take everything I explained above with a grain of salt. Iām in no way an expert on how kotlin works internally, nor on jvm-bytecode. In general my explanation should stand, but pls donāt ask me to explain every part of the bytecode, as I would need to guess myself
Hope this helped.