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.

2 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.