Lambda vs function reference

#1

Hey all, was playing around with byte code when I saw that there’s quite some difference between function referencing and using lambda to call same function. Assume listen is following function:

fun listen(text: String) {
    Log.i("Listener", text)
}

When I pass it as reference as in

 SomeSingleton.listener = ::listen

following byte code is generated:

  SomeSingleton.INSTANCE.setListener((Function1)(new Function1((MainActivity)this) {
     // $FF: synthetic method
     // $FF: bridge method
     public Object invoke(Object var1) {
        this.invoke((String)var1);
        return Unit.INSTANCE;
     }

     public final void invoke(@NotNull String p1) {
        Intrinsics.checkParameterIsNotNull(p1, "p1");
        ((MainActivity)this.receiver).listen(p1);
     }

     public final KDeclarationContainer getOwner() {
        return Reflection.getOrCreateKotlinClass(MainActivity.class);
     }

     public final String getName() {
        return "listen";
     }

     public final String getSignature() {
        return "listen(Ljava/lang/String;)V";
     }
  }));

But if I use it as in

SomeSingleton.listener = {
        listen(it)
}

this is generated instead:

SomeSingleton.INSTANCE.setListener((Function1)(new Function1() {
     // $FF: synthetic method
     // $FF: bridge method
     public Object invoke(Object var1) {
        this.invoke((String)var1);
        return Unit.INSTANCE;
     }

     public final void invoke(@NotNull String it) {
        Intrinsics.checkParameterIsNotNull(it, "it");
        MainActivity.this.listen(it);
     }
  }));

Thus is it, in general practice, more efficient to wrap methods with lambdas instead of method referencing ?

#2

I guess it might be if you write code for android and care about method count. Otherwise the invoke call looks pretty much identical to me. I’m not totally sure how to compare (MainActivity)this.receiver to MainActivity.this but I don’t think there is a measurable performance gain.
Also referencing the function might be more performant if you reference it more than once. I’m not sure that the kotlin compiler can combine multiple identical lambdas into a single object, but ::listen will always only create one class even if you use it multiple times in different places. If you create multiple lambdas { listen(it) } it might create the class for the lambda multiple times, which would be even worse for method count.

But this only matters if you target android and care about dex-count.

#3

Yeah, I asked the same in stackoverflow and got a similar answer. I did not check the output dex to compare after shrinking, but I didn’t add shrinking into equation at all when I was asking the question.

#4

Actually both far from good design. And it’s not about method count. First way produces reflection inner cost and memory leak. Second way produces wrong conditions for multiple clients. For sigletone (i don’t know why do you need a singletone, but ok) it must be like:

object SomeSingletone {
    private val listeners = mutableListOf<(String) -> Unit>()

    fun addListener(listener: (String) -> Unit) {
        listeners.add(init)
    }

    fun removeListener(listener: (String) -> Unit) {
        listeners.remove(init)
    }
}

// in Activity
val listener = { text: String -> Log.d("tag", text) } 
// same as java consumer interface instance without activity reference
// and we have value to remove

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    SomeSingletone.addListener(listener)
}

override fun onDestroy() {
    SomeSingletone.removeListener(listener)
    super.onDestroy()
}