Kotlin has long been the mainstream programming language on Android. One of the reasons why I like this language is that the functions in it are first class objects . That is, a function can be passed as a parameter, used as a return value, and assigned to a variable. Also, instead of a function, you can pass a so-called lambda . And recently I had an interesting problem related to replacing the lambda with a function reference.
Imagine that we have a class Button
that receives a function in the constructor as a parameteronClick
class Button(
private val onClick: () -> Unit
) {
fun performClick() = onClick()
}
And there is a class ButtonClickListener
that implements the logic of button clicks
class ButtonClickListener {
fun onClick() {
print(" ")
}
}
In the class ScreenView
, we store a variable lateinit var listener: ButtonClickListener
and create a button, which is passed a lambda, inside which the method is calledButtonClickListener.onClick
class ScreenView {
lateinit var listener: ButtonClickListener
val button = Button { listener.onClick() }
}
In the method, main
we create an object ScreenView
, initialize the variable listener
and simulate clicking on the button
fun main() {
val screenView = ScreenView()
screenView.listener = ButtonClickListener()
screenView.button.performClick()
}
After starting the application, everything works out fine and the line "Button pressed" is displayed.
ScreenView
, - val button = Button { listener.onClick() }
. , ButtonClickListener.onClick
onClick: () -> Unit
, , , .
class ScreenView {
lateinit var listener: ButtonClickListener
val button = Button(listener::onClick)
}
- listener
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)
, Java . .
Function0
invoke
, . - listener.onClick()
private final Button button = new Button((Function0)(new Function0() {
public final void invoke() {
ScreenView.this.getListener().onClick();
}
}));
, listener
.
. Function0
, invoke()
, , onClick
this.receiver
. receiver
Function0
listener
, listener
lateinit
, receiver
- listener
null
, . .
Button var10001 = new Button;
Function0 var10003 = new Function0() {
public final void invoke() {
((ButtonClickListener)this.receiver).onClick();
}
};
ButtonClickListener var10005 = this.listener;
if (var10005 == null) {
Intrinsics.throwUninitializedPropertyAccessException("listener");
}
var10003.<init>(var10005);
var10001.<init>((Function0)var10003);
this.button = var10001;
That is, the difference between a lambda and a function reference is that when a function reference is passed, the variable whose method we are referring to is fixed at creation, and not at execution, as happens when a lambda is passed.
This leads to the following interesting problem: What will be printed after the program starts?
class Button(
private val onClick: () -> Unit
) {
fun performClick() = onClick()
}
class ButtonClickListener(
private val name: String
) {
fun onClick() {
print(name)
}
}
class ScreenView {
var listener = ButtonClickListener("First")
val buttonLambda = Button { listener.onClick() }
val buttonReference = Button(listener::onClick)
}
fun main() {
val screenView = ScreenView()
screenView.listener = ButtonClickListener("Second")
screenView.buttonLambda.performClick()
screenView.buttonReference.performClick()
}
FirstFirst
FirstSecond
SecondFirst
SecondSecond
Answer
3
Thanks for reading, I hope someone was interested and useful!