Method Reference vs Lambda

Recently I had a problem related to replacing lambda with method reference. Imagine that we have Button class which takes listener into constructor

    class Button(
    	private val onClick: () -> Unit
    ) {
    	fun performClick() = onClick()
    }

Also we have ButtonClickListener class which represents button click logic

    class ButtonClickListener {
    	fun onClick() {
    	   println("Button clicked")
    	}
    }

In ScreenView class we have lateinit property lateinit var listener: ButtonClickListener and we create button using lamda which calls listener’s method into it

    class ScreenView {
        lateinit var listener: ButtonClickListener
        val button = Button { listener.onClick() }
    }

In main method we initialize listener and call performClick method on Button

    fun main() {
        val screenView = ScreenView()
        screenView.listener = ButtonClickListener()
        screenView.button.performClick()
    }

In this case program runs successfully and prints “Button clicked”, but after replacing onClick with method reference val button = Button { listener.onClick() } → val button = Button(listener::onClick) program crashes with exception

     Exception in thread "main" kotlin.UninitializedPropertyAccessException: lateinit property listener has not been initialized
     at lambdas.ScreenView.<init>(ScreenView.kt:6)
     at lambdas.ScreenViewKt.main(ScreenView.kt:10)
     at lambdas.ScreenViewKt.main(ScreenView.kt)

After decompiling kotlin code to java we find this difference. If we pass lambda to Button’s constructor, lamda is replaced with anonymous class

   private final Button buttonLambda = new Button((Function0)(new Function0() {
      // $FF: synthetic method
      // $FF: bridge method
      public Object invoke() {
         this.invoke();
         return Unit.INSTANCE;
      }

      public final void invoke() {
         ScreenView.this.getListener().onClick();
      }
   }));

But if we use method reference then button initializing is different

      Button var10001 = new Button;
      Function0 var10003 = new Function0() {
         // $FF: synthetic method
         // $FF: bridge method
         public Object invoke() {
            this.invoke();
            return Unit.INSTANCE;
         }

         public final void invoke() {
            ((ButtonClickListener)this.receiver).onClick();
         }

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

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

         public final String getSignature() {
            return "onClick()V";
         }
      };
      ButtonClickListener var10005 = this.listener;
      if (var10005 == null) {
         Intrinsics.throwUninitializedPropertyAccessException("listener");
      }

      var10003.<init>(var10005);
      var10001.<init>((Function0)var10003);
      this.buttonMethodReference = var10001;

And on this line program crashes

      if (var10005 == null) {
         Intrinsics.throwUninitializedPropertyAccessException("listener");
      }

So my question is why java code that we have got after using method reference is different than after using lamda? Can’t we always create anonymous class both for method reference and lamda?

listener::onClick is a bound method reference, that is a reference to the method ButtonClickListener::onClick bound to a particular instance of ButtonClickListener, namely listener. That instance is obtained at the moment the reference is created, not at the moment it is called.

Can’t we always create anonymous class both for method reference and lamda?

No, because it would change the behavior of the bound method references above. While it might be useful in your case, there are other usages that rely on the fact that the instance to which reference binds is evaluated only once — these usages would break in the result of such change.

3 Likes

@ilya.gorbunov Thanks for the answer. As far as I understood the fact that instance of method reference is obtained at the moment the reference is created is dictated by the language design. Maybe there is some literature about programming languages where is written why the default behavior is exactly like this? I just want to understand the logic why language creators decided to do it that way? Hope I was able to explain my thoughts in questions.

Came here because I just spent a long time debugging an error caused by this behavior.

@ilya.gorbunov I understand the distinction you’re pointing out, and certainly respect the desire not to break backwards compatibility. However, my concern is that this subtle difference is not documented very well – In fact, the language docs strongly imply that fun a(x: String) = b.c(x) and fun a = b::c are equivalent. See: Reflection | Kotlin Documentation . This makes me wonder if the binding characteristics of the method reference are more of an implementation detail than an intentional feature of the Kotlin specification.

there are other usages that rely on the fact that the instance to which reference binds is evaluated only once

I’d be curious if you have an example of where idiomatic Kotlin relies on this behavior.

I don’t know what was the main reason to design it this way, but honestly, I would be really surprised and confused if it would work differently than right now. Every code that is part of an expression and isn’t inside some { .. } block is always executed directly and immediately. So it would be very strange and inconsistent with the rest of the language if b::c expression would work differently and its resolution would be postponed.

a + b // get current value of a and b and sum them
{ a + b } // only create lambda, get values later, when it is executed
a::b // get current value of a and its member b
{ a.b } // create lambda, resolve a and b when it is executed

I’m always confused with Scala with its: map(_.name), because it looks for me as the _.name expression is executed directly, not passed anywhere.

1 Like