Шпаргалка по функциям Kotlin: also, let, apply, with, run
Во время перехода с Java на Kotlin в своих проектах мне было удобнее писать код в Java стиле. Из-за этого я не использовал многие возможности Kotlin, что лишало меня многих преимуществ этого чудесного языка, которые должны были, по идее, упростить мне, как разработчику, жизнь и добавить лаконичности моему коду.
Одной из таких изюминок языка, без которых прожить можно, но которые все же хотелось освоить и понять, была коллекция стандартных вспомогательных функций — also, let, apply, with и run. Эти функции похожи, но у каждой есть своё поведение, отличающееся от остальных. Пусть эта статья будет шпаргалкой, в которой я постараюсь собрать вместе информацию об этих небольших помощницах.
Начнём с функции apply. Её назначением можно считать настройку объекта получателя, на котором она вызывается, для дальнейшего использования. Результатом её работы является сам объект получатель (тот, на котором она была вызвана). Для примера давайте возьмём создание сокета. В обычной ситуации нам для его настройки необходимо создать экземпляр класса, после чего вызвать на нём необходимые методы. Что-то вроде этого:
val socket = Socket() socket.keepAlive = false socket.receiveBufferSize = 2048 socket.tcpNoDelay = true
С apply же всё можно сделать немного короче:
val socket = Socket() socket.keepAlive = false socket.receiveBufferSize = 2048 socket.tcpNoDelay = true
Результат работы кода будет одним и тем же, но с использованием apply мы избавляемся от повторного написания имени переменной socket, так как внутри лямбды, переданной в функцию apply, все вызовы будут выполняться на объекте получателе socket:
val socket = Socket().apply { keepAlive = false //то же самое, что socket.keepAlive = false receiveBufferSize = 2048 //то же самое, что socket.receiveBufferSize = 2048 tcpNoDelay = true //то же самое, что socket.tcpNoDelay = true }
В библиотеке котлина функция apply выглядит так:
inline fun T.apply(block: T.() -> Unit): T
Следующая функция — let. let создаёт область видимости, в которой можно сослаться на объект получатель, используя слово it.
Красота работы let раскрывается в связках с другими возможностями языка, например с null-типами. Мы можем без лишних присвоений и веток if написать что-то вроде такого:
listOf(0, 1, 2, null, 4, null, 6, 7).forEach { it?.let { println(it) } ?: println("null detected") }
Перебираем массив, содержащий Int?, и обрабатываем элементы в зависимости от того, являются они null или нет.
Результатом работы let будет являться последняя строка переданной в него лямбды (в отличии от apply, в которой результатом является объект-получатель, на котором вызывается функция).
В библиотеке функция описана так:
inline fun <T, R> T.let(block: (T) -> R): R
Теперь давайте разберёмся с функцией also, которая очень похожа на let и имеет лишь одно отличие — она возвращает объект-получатель (в отличие от let, которая возвращает результат последней строки лямбды). Это отличие позволяет создавать цепочки вызовов.
К примеру, нам понадобилось прологгировать обработку данных списка, которые сохраняются в файл.
listOf(0, 1, 2, null, 4, null, 6, 7).forEach { it?.also { println(it) }?.also { saveToFile(it.toString()) } ?: println("null detected") }
Ну и сама функция:
inline fun <T> T.also(block: (T) -> Unit): T
Теперь run. Эта функция, как и apply, ограничивает область видимости, позволяя в лямбде делать вызовы функций объекта-получателя напрямую. Но run, в отличии от apply, возвращает результат работы лямбды. Интересная возможность этой функции — потоковый вызов ссылок на функции. Чтобы понять это, создадим несколько простых функций, а затем вызовем их в Java и Kotlin стилях.
3 примитивные функции будут имитировать проверку ответа с сервера, выбор сообщения и его печать.
fun checkServerResponse(code: Int): Boolean = code == 200 fun showServerResponseMessage(isCodeValid: Boolean) = if (isCodeValid) { "с сервером всё в порядке" } else { "с сервером есть проблемы, выходные в опасности" } fun printMessage(message: String) = println(message)
Вызов функций в Java стиле:
printMessage(showServerResponseMessage(checkServerResponse(200)))
Вызов функций в Kotlin стиле:
200 .run(::checkServerResponse) .run(::showServerResponseMessage) .run(::printMessage)
В Java стиле приходится читать вызовы справа налево, тогда как в потоковом вызове котлина функции располагаются в аккуратном порядке их вызова сверху вниз. В библиотеке котлина функция выглядит так:
inline fun <T, R> T.run(block: T.() -> R): R
Есть ещё один её вариант, который можно вызывать без объекта получателя, но используется она гораздо реже.
inline fun <R> run(block: () -> R): R
Последняя в нашем списке — with. По её объявлению понятно, что объект-получатель передаётся ей в первом аргументе, чем она отличается от первых четырёх функций, описанных в этой статье.
inline fun <T, R> with(receiver: T, block: T.() -> R): R
Пример использования:
val startWithSpace = with(" Зима, холода, одинокие дома") { if (startsWith(" ")) { "строка начинается с пробела" } else { "корректное начало строки" } }
Эта функция идиоматически отличается от первых четырёх, и, возможно, стоит вместо неё использовать run.
Использование этих функций несомненно внесёт вклад в красоту и читаемость кода, поможет программисту начать писать код в Kotlin-стиле.
Спасибо за внимание.
Если заметите ошибки или будет желание дополнить статью — обязательно пишите мне на почту или в комментариях, всегда буду рад пообщаться 🙃. В заключение приведу сравнительную таблицу.
Автор статьи – Максим Казанцев, участник курсов DEV-Intensive по Android на Kotlin и Android Middle от SkillBranch.
Источник: also let apply with run. Шпаргалка по функциям Kotlin