I started wondering whether accessing a property via ::test
is equivalent to calling { test }
or whether it is rather an indirect call using reflection.
The question came to my mind when looking at the following: How can I pass property getter as a function type to another function
While both ::test
and { test }
work, the IDE (Intellij) set the ::test
to a KProperty
-type whereas the latter type was () -> String
when assigned to a variable. So there is a difference here. But what would be the effective difference? Are these real method references as in Java or are they rather a reflection way to access properties? Will one variant probably have any performance impact over the other?
Code snippet:
class Test(val test : String) {
fun testFun(func: ()->String) : String = func()
fun callTest() {
testFun { test } // or (::test) // is it using reflection? are these real references?
}
}
I think in this case it's better to check the bytecode to see what's going on.
I used the following code:
class Test(val test: String) {
fun testFun(func: () -> String): Unit = TODO()
fun callTest() {
testFun { test }
testFun(::test)
}
}
For testFun { test }
here's the generated bytecode:
ALOAD 0
NEW Test$callTest$1
DUP
ALOAD 0
INVOKESPECIAL Test$callTest$1.<init> (LTest;)V
CHECKCAST kotlin/jvm/functions/Function0
INVOKEVIRTUAL Test.testFun (Lkotlin/jvm/functions/Function0;)V
And here's the bytecode for testFun(::test)
:
ALOAD 0
NEW Test$callTest$2
DUP
ALOAD 0
CHECKCAST Test
INVOKESPECIAL Test$callTest$2.<init> (LTest;)V
CHECKCAST kotlin/jvm/functions/Function0
INVOKEVIRTUAL Test.testFun (Lkotlin/jvm/functions/Function0;)V
They look almost exactly the same, except that the first one is creating a Test$callTest$1
, while the second is using a Test$callTest$2
. There's also an extra CHECKCAST Test
in the second "version" because Test$callTest$2
is expecting an instance of Test
in its constructor.
So, what's the difference between $1
and $2
?
Here's $1
version:
final class Test$callTest$1 extends kotlin/jvm/internal/Lambda implements kotlin/jvm/functions/Function0 {
....
final synthetic LTest; this$0 // reference to your Test instance
<init>(LTest;)V {
ALOAD 0
ALOAD 1
PUTFIELD Test$callTest$1.this$0 : LTest;
ALOAD 0
ICONST_0 // Lambda arity is zero
INVOKESPECIAL kotlin/jvm/internal/Lambda.<init> (I)V
RETURN
}
public final String invoke() {
ALOAD 0
GETFIELD Test$callTest$1.this$0 : LTest;
INVOKEVIRTUAL Test.getTest ()Ljava/lang/String;
}
}
While $2
:
final class Test$callTest$2 extends kotlin/jvm/internal/PropertyReference0 {
...
<init>(LTest;)V {
ALOAD 0
ALOAD 1
INVOKESPECIAL kotlin/jvm/internal/PropertyReference0.<init> (Ljava/lang/Object;)V
RETURN
}
public Object get() {
GETFIELD Test$callTest$2.receiver : Ljava/lang/Object;
CHECKCAST Test
INVOKEVIRTUAL Test.getTest ()Ljava/lang/String;
ARETURN
}
}
So it seems there's no big difference in terms of bytecode instructions.
EDIT:
Class $2
inherits an invoke
method from its parent class PropertyReference0
that calls its get()
method, while $1
immediately declares invoke
. Because of that, without further optimization $2
performs one extra method call compared to $1
.