Содержание
Некоторые стандартные функции в Kotlin настолько похожи, что сложно определиться, какую из них использовать. Здесь мы попробуем четко различить их различия и выбрать, какую из них использовать.
Функции определения области видимости
Я сосредоточусь на функциях run , with , T.run , T.let , T.also и T.apply . Я называю их функциями области видимости, так как считаю, что их основная задача заключается в обеспечении внутренней области видимости для вызывающей функции. Самый простой способ проиллюстрировать область видимости — запустить функцию:
fun test() { var mood = "Мне грустно" run { val mood = "Я счастлив" println (mood) // Я счастлив } println (mood) // Мне грустно }
Внутри функции test у вас может быть отдельная область, в которой mood
переопределяется в "Я счастлив"
перед печатью, и она полностью заключена в область run. Эта функция обзора кажется не очень полезной. Но есть одна приятная деталь, она возвращает последний объект в области видимости. Благодаря чему мы можем применить любую функцию (например show()
) к обоим элементам, не записывая эту функцию “show()
” дважды. Вот пример:
run { if (firstTimeView) introView else normalView }.show()
Разница между функциями области видимости
Нормальная функция и функция расширения
Если мы посмотрим на with
и T.run
, обе функции очень похожи. Ниже они выполняют одинаковые действия:
with(webview.settings) { javaScriptEnabled = true databaseEnabled = true } webview.settings.run { javaScriptEnabled = true databaseEnabled = true }
Однако их различие заключается в том, что одна из этих функций является – нормальной функцией “with
“, а другая – функцией расширения “T.run
” .
Итак, вопрос в том, в чем преимущество каждого из них? Представьте, что если webview.settings
могли бы быть null, запись выглядела бы так:
with(webview.settings) { this?.javaScriptEnabled = true this?.databaseEnabled = true } webview.settings?.run { javaScriptEnabled = true databaseEnabled = true }
В этом случае функция расширения T.run выглядит предпочтительнее, так как мы можем применить проверку на допустимость значения NULL в одном месте.
Аргументы this и it
Если мы посмотрим на T.run
и T.let
, обе функции будут похожи, за исключением того, что T.let
принимает аргумент it. Ниже показана одинаковая логика для обеих функций:
stringVariable?.run { println("Длина этой строки равна $length") } stringVariable?.let { println("Длина этой строки равна ${it.length}") }
Если вы присмотритесь то увидите, что T.run
сделана как вызов функции расширения. Следовательно, все, что находится в пределах области действия, T
может называться this
. В Kotlin, this
в большинстве случаев может быть опущено, поэтому в нашем примере выше мы cмогли написать просто $length
вместо ${this.length}
.
А вот T.let
отправляет себя в функцию и это похоже на лямбда-аргумент. На него можно ссылаться в области видимости как it
. Кажется, что T.run
выглядит лучше, чем T.let
, но есть некоторые преимущества T.let
:
T.let
обеспечивает более четкое различие между использованием переменной данной функции и переменных функций внешнего класса.this
нельзя опустить, например, когда он отправляется как параметр функции, и в этом случаеit
писать короче чемthis
и понятнее.T.let
позволяет лучше именовать преобразованную используемую переменную, т.е. вы можете преобразоватьit
в другое имя, как показано ниже:
stringVariable?.let { nonNullString -> println("Ненулевая строка $nonNullString") }
Возвращать this или другие типы
Теперь давайте посмотрим на T.let
и T.also
, оба идентичны, если мы посмотрим на область его внутренней функции:
stringVariable?.let { println("Длина этой строки равна ${it.length}") } stringVariable?.also { println("Длина этой строки равна ${it.length}") }
Однако их тонкая разница заключается в том, что они возвращают разный тип значений. Простой пример, демонстрирующий это:
val original = "abc" // let original.let { println("Исходная строка $it") // "abc" it.reversed() }.let { println("Обратная строка $it") // "cba" it.length }.let { println("Длина строки $it") // 3 } // also original.also { println("Исходная строка $it") // "abc" }.also { println("Обратная строка ${it.reversed()}") // "cba" }.also { println("Длина строки ${it.length}") // 3 }
T.also
может показаться бессмысленным, так как мы могли бы легко объединить их в единый функциональный блок. Но у T.also
есть хорошие преимущества:
T.also
может обеспечить очень четкий процесс разделения на одних и тех же объектах, т.е. создание меньших функциональных секций.T.also
может быть очень удобным для выполняя операции построения цепочки.
Пример использования T.
let и T.
also вместе:
// Нормальный подход fun makeDir(path: String): File { val result = File(path) result.mkdirs() return result } // Улучшенный подход fun makeDir(path: String) = path.let{ File(it) }.also{ it.mkdirs() }
Apply
Теперь рассмотрим функцию T.apply:
- Это функция расширения.
- Она отправляется
this
как аргумент. - Она возвращает
this
(т.е. себя).
Вот пример применения apply:
// Нормальный подход fun createIntent(intentData: String, intentAction: String): Intent { val intent = Intent() intent.action = intentAction intent.data=Uri.parse(intentData) return intent } // Улучшенный подход, цепочка fun createIntent(intentData: String, intentAction: String) = Intent().apply { action = intentAction } .apply { data = Uri.parse(intentData) }
Заключение
Для правильного выбора функции можно воспользоваться следующей шпаргалкой:
Надеемся, что я помог вам надлежащим образом разобраться с использованием этих функций.