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?