[Разработка под Android, Kotlin] Kotlin. Лямбда vs Ссылка на функцию
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Kotlin уже давно стал основным языком программирования на Android. Одна из причин, почему мне нравится этот язык, это то, что функции в нем являются объектами первого класса. То есть функцию можно передать как параметр, использовать как возвращаемое значение и присвоить переменной. Также вместо функции можно передать так называемую лямбду. И недавно у меня возникла интересная проблема, связанная с заменой лямбды ссылкой на функцию.Представим, что у нас есть класс Button, который в конструкторе получает как параметр функцию onClick
class Button(
private val onClick: () -> Unit
) {
fun performClick() = onClick()
}
И есть класс ButtonClickListener, который реализует логику нажатий на кнопку
class ButtonClickListener {
fun onClick() {
print("Кнопка нажата")
}
}
В классе ScreenView у нас хранится переменная lateinit var listener: ButtonClickListener и создается кнопка, которой передается лямбда, внутри которой вызывается метод ButtonClickListener.onClick
class ScreenView {
lateinit var listener: ButtonClickListener
val button = Button { listener.onClick() }
}
В методе main создаем объект ScreenView, инициализируем переменную listener и имитируем нажатие по кнопке
fun main() {
val screenView = ScreenView()
screenView.listener = ButtonClickListener()
screenView.button.performClick()
}
После запуска приложения, все нормально отрабатывает и выводится строка "Кнопка нажата".А теперь давайте вернемся в класс 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;
То есть разница между лямбдой и ссылкой на функцию заключается в том, что при передаче ссылки на функцию, переменная, на метод которой мы ссылаемся, фиксируется при создании, а не при выполнении, как это происходит при передаче лямбды.Отсюда вытекает следующая интересная задача: Что напечатается после запуска программы?
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
Ответ3Спасибо за прочтение, надеюсь кому-то было интересно и полезно!
===========
Источник:
habr.com
===========
Похожие новости:
- [Хостинг, Разработка веб-сайтов, Разработка под Android, API, Транспорт] Как реализовать отслеживание местоположения андроид устройства на своем сайте
- [Программирование, Разработка под Android, Kotlin] О взаимосвязи между корутинами, потоками и проблемами параллелизма (перевод)
- [Отладка, Микросервисы, Serverless] Руководство по отладке бессерверных приложений (перевод)
- [Игры и игровые приставки] Эмулятор PS2 на Android — вторая серия
- [Разработка под Android] Получаем результат правильно (Часть 1). Activity Result API
- [Open source, Разработка под Android, Kotlin] Reaction — обработка результатов методов в Kotlin
- [Разработка под iOS, Разработка под Android, Управление продажами, Презентации] Онбординг. Зачем нужен и как использовать
- [Java, Разработка мобильных приложений, Разработка под Android, Kotlin] Android — ViewPager2 — заменяем фрагменты на лету (программно)
- [Высокая производительность, Разработка мобильных приложений, IT-инфраструктура, Разработка под Android] Как мы ускорили запуск приложения Dropbox для Android на 30 % (перевод)
- [Программирование, Разработка мобильных приложений, Разработка под Android, Kotlin] Влияние data-классов на вес приложения
Теги для поиска: #_razrabotka_pod_android (Разработка под Android), #_kotlin, #_kotlin, #_kotlin_android, #_lambda, #_lambdas, #_method_reference, #_android, #_android_development, #_razrabotka_pod_android (
Разработка под Android
), #_kotlin
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 23:24
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Kotlin уже давно стал основным языком программирования на Android. Одна из причин, почему мне нравится этот язык, это то, что функции в нем являются объектами первого класса. То есть функцию можно передать как параметр, использовать как возвращаемое значение и присвоить переменной. Также вместо функции можно передать так называемую лямбду. И недавно у меня возникла интересная проблема, связанная с заменой лямбды ссылкой на функцию.Представим, что у нас есть класс Button, который в конструкторе получает как параметр функцию onClick class Button(
private val onClick: () -> Unit ) { fun performClick() = onClick() } class ButtonClickListener {
fun onClick() { print("Кнопка нажата") } } class ScreenView {
lateinit var listener: ButtonClickListener val button = Button { listener.onClick() } } fun main() {
val screenView = ScreenView() screenView.listener = ButtonClickListener() screenView.button.performClick() } class ScreenView {
lateinit var listener: ButtonClickListener val button = Button(listener::onClick) } 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) private final Button button = new Button((Function0)(new Function0() {
public final void invoke() { ScreenView.this.getListener().onClick(); } })); 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; 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() }
=========== Источник: habr.com =========== Похожие новости:
Разработка под Android ), #_kotlin |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 23:24
Часовой пояс: UTC + 5