also let apply with run. Шпаргалка по функциям Kotlin.

Во время перехода с 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().apply {
     keepAlive = false
     receiveBufferSize = 2048
     tcpNoDelay = true
 }

Результат работы кода будет одним и тем же, но с использованием apply мы избавляемся от повторного написания имени переменной socket, так как внутри лямбды, переданной в функцию apply, все вызовы будут выполняться на объекте получателе.

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 serverResponseShowMessage(codeIsValid:Boolean) = if (codeIsValid){
         "с сервером всё в порядке"
     } else {
         "с сервером есть проблемы, выходные в опасности"
     }
fun printMessage(message:String) = println(message)

Вызов функций в java стиле:

printMessage(serverResponseShowMessage(checkServerResponse(200)))

kotlin стиль:

200
     .run(::checkServerResponse)
     .run(::serverResponseShowMessage)
     .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-стиле. Спасибо за внимание, если заметите ошибки или будет желание дополнить статью — обязательно пишите мне на почту или в комментариях, всегда буду рад пообщаться🙃 В заключении приведу сравнительную таблицу.

Имя функцииОпределениеЧто возвращаетОбращение в лямбде к объекту получателю по itОграничивает область видимости
applyinline fun T.apply(block: T.() -> Unit): Tобъект — получатель+
letinline fun <T, R> T.let(block: (T) -> R): Rрезультат лямбды+
alsoinline fun <T> T.also(block: (T) -> Unit): T объект — получатель+
runinline fun <T, R> T.run(block: T.() -> R): Rрезультат лямбды+
withinline fun <T, R> with(receiver: T, block: T.() -> R): Rрезультат лямбды+

Один комментарий

Добавить комментарий для KostyaОтменить ответ

Ваш адрес email не будет опубликован. Обязательные поля помечены *