Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is the property reference (::test) equivalent to a function accessing the property ({ test }) when passed as argument e.g. `() -> String`?

Tags:

kotlin

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?
  }
}
like image 849
Roland Avatar asked Aug 14 '18 12:08

Roland


1 Answers

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.

like image 112
user2340612 Avatar answered Sep 28 '22 20:09

user2340612