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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With