with, apply, also, run, let
Функция with позволяет выполнить несколько операций над одним объектом, не повторяя его имени.
Функция принимает два аргумента — объект и лямбда-выражение. Первый аргумент преобразуется в получатель лямбда-выражения. К получателю можно обращаться через this.
class Cat < var name:String? = null var breed:String? = null >val murzik = Cat() murzik.name = "Murzik" // Вместо val murzikName = murzik.name val murzikBreed = murzik.breed println("$murzikName:$murzikBreed") // Укороченный вариант with(murzik)
Нам уже не нужно каждый раз упоминать имя объекта.
Ещё один пример посложнее.
// выводим все буквы алфавита fun printAlphabet() = with(StringBuilder()) < for (letter in 'A'..'Z')< append(letter) >toString() > println(printAlphabet()) // ABCDEFGHIJKLMNOPQRSTUVWXYZ
Функция возвращает результат последнего выражения в теле лямбда-выражения. Если вам нужен объект-получатель, то используйте apply.
apply
Функция apply работает почти так же, как with, но возвращает объект, переданный в аргументе.
fun printAlphabet() = StringBuilder().apply < for (letter in 'A'..'Z')< append(letter) >>.toString() println(printAlphabet())// ABCDEFGHIJKLMNOPQRSTUVWXYZ
Полезна в тех случаях, когда требуется создание экземпляра, у которого следует инициализировать некоторые свойства. Часто в этих случаях мы просто повторяем имя экземпляра.
val button = findViewById(R.id.button) button.text = "I am a button" button.textSize = 18.0F button.setBackgroundColor(Color.RED)
Инициализируем настройки кнопки через apply.
val button = findViewById(R.id.button) button.apply
Применим к коту, поменяв возраст.
class Cat(var age: Int) val result = Cat(5).apply < age = 8 >println(result.age) // 8
also
Метод для обмена значениями между двумя переменными без участия третьей переменной.
var x = 100 var y = 25 x = y.also < y = x >println(x) println(y) class Cat(var age: Int) val result = Cat(5).also < it.age = 8 >println(result.age) // 8
val game: String = StringBuilder().also < it.append("tic") it.append("tac") it.append("toe") >.toString() println(game)
let
let полезен при работе с объектами, которые могут принимать значение null. Вместо того, чтобы создавать длинные цепочки выражений if-else, можно просто скомбинировать оператор ? («оператор безопасного вызова») с let: в результате мы получим лямбду, у которой аргумент it является не nullable-версией исходного объекта.
var cat: String? = null cat?.let // не выводится cat = "Barsik" cat?.let
Второй вариант использования — преобразовать один тип в другой.
run
Узнаем настроение кота.
fun testCat() < var mood = "I am sad" run < val mood = "I am happy" println(mood) // I am happy >println(mood) // I am sad > Logcat: I/System.out: I am happy I/System.out: I am sad
В функции дважды используется переменная с одним именем, но они не мешают друг другу и выводят разные сообщения, не переопределяя своих значений.
Это был простейший и достаточно бесполезный пример. В реальности он используется немного иначе.
Let kotlin что это
Шаг 67.
Основы Kotlin.
Null-безопасность и исключения. Null-безопасность. Использование безопасного вызова с let()
На этом шаге мы рассмотрим пример использования этой функции .
Безопасный вызов позволяет вызвать функцию с типом, поддерживающим null , но что если вы хотите выполнить дополнительную работу, например, ввести новое значение или вызвать другие функции, если значение переменной отлично от null ? Один из способов достичь этого — использовать оператор безопасного вызова с функцией let() . Эта функция вызывается со значением, и суть в том, чтобы разрешить объявлять переменные для выбранной области видимости.
Так как let() создает свою область видимости, можно использовать безопасный вызов с let() , чтобы охватить несколько выражений, которые требуют переменной с типом, не поддерживающим null . В свое время мы поговорим об этом подробнее, а сейчас изменим определение beverage , чтобы увидеть, как это делается.
fun main() < var beverage = readLine()?.let < if (it.isNotBlank()) < it.capitalize() > else < "Buttered Ale" > > // beverage = null println(beverage) >
Файл с проектом можно взять здесь.
Рис.1. Использование let с оператором безопасного вызова (Tavern.kt)
Здесь вы объявляете beverage как переменную с типом, поддерживающим null . Но в этот раз присваиваете значение как результат безопасного вызова let() . Когда beverage имеет значение, отличное от null , вызывается let() и выполняется тело анонимной функции, переданной в let() : проверяются входные данные из readLine() и определяется, было ли введено что-то пользователем; если да, то первая буква становится прописной, а если нет, то возвращается запасное название напитка — «Buttered Ale» . Функции isNotBlank() и capitalize() требуют, чтобы аргумент имел тип, не поддерживающий null , что обеспечит функция let() .
Функция let() поддерживает несколько соглашений, двумя из которых вы воспользовались здесь. Во-первых, в объявлении beverage вы использовали ключевое слово it , с которым познакомились чуть позже. Внутри let() ключевое слово it ссылается на переменную, для которой вызвана let() , и гарантирует, что она имеет тип, не поддерживающий null . Вы вызываете isNotBlank() и capitalize() с it — формой beverage , не поддерживающей null .
Второе соглашение let() не так очевидно: let() возвращает результаты в неявной форме, поэтому вы можете (и должны) присвоить этот результат переменной сразу после вычисления вашего выражения.
Запустите Tavern.kt и задайте значение null переменной beverage , просто нажав клавишу Enter .
Рис.2. Результат работы приложения при заданном значении null
Когда beverage имеет значение, отличное от null , вызывается let() , появляется прописная буква и выводится результат.
Рис.3. Результат работы приложения при заданном значении, отличном от null
Когда beverage имеет значение null , функция let() не вызывается, и в beverage остается null .
На следующем шаге мы рассмотрим использование оператора . .
Функции области видимости (Scope Function) в Kotlin
Сразу оговорюсь, что статья объясняет базовые понятия и если вы уже программируете на Kotlin, то скорее всего вы уже все знаете. Большая часть того, что приведено в статье, освещено в официальной документации, поэтому статью можно рассматривать как дополнительный материал к ней.
В статье используется термин «функции области видимости» для «Scope Function». Это определение взято из перевода документации на русский язык.
По контекстным функциям в Kotlin есть много информации, включая на русском. Часть таких статей приведены в использованных материалах.
- Что такое функции области видимости
- Функции расширения
- Как работают функции области видимости
- Что когда применять
- Использованные материалы
Что такое функции области видимости
В Kotlin есть 5 функций: let , run , with , apply и also , объединенных общим названием Scope Function (функции области видимости). Все они используются для одной цели — выполнить какой-то блок кода для конкретного объекта. Почему их так назвали? Потому что они меняют способ взаимодействия и видимость для этой переменной.
В основном они отличаются только 2 параметрами: способом ссылки на объект и возвращаемым параметром.
Давайте сначала приведем пример использования:
let
val length = "test".let
- Объект «test» внутри блока доступен как it
- Возвращает результат выполнения lambda-функции
also
val test = "test".also
- Объект «test» внутри блока доступен как it
- Возвращает контекстный объект ( «test» )
apply
val moscow = City("Moscow").apply
- Объект City(«Moscow») внутри блока доступен как this (поэтому для поля popultaion — мы можем опустить обращения и будет population=15_000_000 )
- Возвращает контекстный объект (изменённый City(«Moscow») )
run (с контекстным объектом)
val optimalSquare = City("Moscow").run
- Объект City(«Moscow») внутри блока доступен как this (поэтому для поля popultaion — мы можем опустить обращения и будет population=15_000_000 )
- Возвращает результат выполнения lambda-функции ( solveOptimalSquare() )
run (без контекстного объекта)
val length = run
- Нет объекта на котором применятся
- Возвращает результат выполнения lambda-функции ( test.length )
with
val length = with("test")
- Объект «test» внутри блока доступен как this
- Возвращает результат выполнения lambda-функции ( this.length )
Как видно, функции очень похожи друг на друга. Для того чтобы разобраться как они работают нужно разобраться в понятии extension function (здесь и далее будет использован перевод «функции расширения»)
Функции расширения
Функции расширения в Kotlin позволяют расширять классы, не наследуясь от них. С помощью них мы можем добавить к существующим классам свои методы. Функции расширения таким образом заменяют утилитные классы (например, StringUtils от Apache).
Давайте рассмотрим упрощенный пример из стандартной библиотеки Kotlin
public fun CharSequence?.isNullOrBlank(): Boolean
Как видно в качестве класса для расширения также можно использовать null-допустимые классы.
Как это работает:

- Мы указываем тип получателя (reciever type)
- Ссылаемся на объект этого типа как this
Давайте посмотрим, во что компилируется функция расширения.
fun main() < println("test".firstSymbol()) >public fun String.firstSymbol(): Char
public static final char firstSymbol(java.lang.String); descriptor: (Ljava/lang/String;)C flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL Code: stack=2, locals=1, args_size=1 0: aload_0 1: ldc #27 // String 3: invokestatic #33 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V 6: aload_0 7: iconst_0 8: invokevirtual #39 // Method java/lang/String.charAt:(I)C 11: ireturn LineNumberTable: line 6: 6 LocalVariableTable: Start Length Slot Name Signature 0 12 0 $this$firstSymbol Ljava/lang/String; RuntimeInvisibleParameterAnnotations: parameter 0: 0: #25() org.jetbrains.annotations.NotNull
public static final char firstSymbol(@NotNull String $this$firstSymbol)
Как видно, она комплируется в статический метод, где первым параметром выступает объект, на котором применяется функция расширения.
Как раз поэтому функция расширения не может получить доступ к приватным полям и методам и поэтому функции расширения вычиляются статически.
//Код НЕ рабочий fun main() < println(Test("test").firstSymbol()) >class Test(private val value: String) public fun Test.firstSymbol(): Char< // поле является приватным и из статического метода к нему нет доступа return this.value[0] //ОШИБКА >
Следующий пример взят из документации
open class Shape class Rectangle: Shape() fun Shape.getName() = "Shape" fun Rectangle.getName() = "Rectangle" fun printClassName(s: Shape) < println(s.getName()) >printClassName(Rectangle())

Как работают функции области видимости
После того, как мы разобрались с функциями расширения, самое время взглянуть, что под капотом функций области видимости.
Для начала приведем исходный код всех рассматриваемых функций и разберем его
let
public inline fun T.let(block: (T) -> R): R
let — сама является функцией расширения и принимает обычную lambda-функцию, которая вызывается с параметром this (объектом, на котором вызывается let ). Так как block — это обычная lambda-функция, то единственный аргумент в ней доступен как it . Возвращается результат выполнения block(this)
also
public inline fun T.also(block: (T) -> Unit): T
also — очень похож на let , но возвращается объект this (объект, на котором вызывается also)
apply
public inline fun T.apply(block: T.() -> Unit): T
Функция apply устроена довольно интересно. Она является функцией расширения, при этом как параметр она принимет lambda-функцию, которая тоже является расширением для того же типа. Поэтому вызов block() здесь нужно рассматривать как вызов this.block() . Возвращается объект, на котором была вызвана функция apply
run (с контекстным объектом)
public inline fun T.run(block: T.() -> R): R
run очень похож на apply, но возвращает результат выполнения this.block()
run (без контекстного объекта)
public inline fun run(block: () -> R): R
Не является функцией расширения, и принимет обычную lambda-функцию. Возвращает результат выполнения этой lambda-функции.
with
public inline fun with(receiver: T, block: T.() -> R): R
Не является функцией расширением. Принимает два параметра — объект и функция расширения, которая будет вызываться на нем. Возвращает результат этого выполнения.
Как можно заметить, все функции области видимости являются inline , то есть их тело вставляется в место, где они вызываются, что позволяет исключить накладные расходы на вызов метода.
fun main() < val length = "test".let < println(it) it.length >println(length) >
public static final void main(); descriptor: ()V flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL Code: stack=2, locals=7, args_size=0 0: ldc #8 // String test 2: astore_1 3: iconst_0 4: istore_2 5: iconst_0 6: istore_3 7: aload_1 8: astore 4 10: iconst_0 11: istore 5 13: iconst_0 14: istore 6 16: getstatic #14 // Field java/lang/System.out:Ljava/io/PrintStream; 19: aload 4 21: invokevirtual #20 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V 24: aload 4 26: invokevirtual #26 // Method java/lang/String.length:()I 29: nop 30: istore_0 31: iconst_0 32: istore_1 33: getstatic #14 // Field java/lang/System.out:Ljava/io/PrintStream; 36: iload_0 37: invokevirtual #29 // Method java/io/PrintStream.println:(I)V 40: return
Близко-соответствующий java-code (часть служебных переменных удалено):
public static final void main()
Как видно, здесь нет никакого упоминания об let
Что когда применять
Какую функцию когда применять — вопрос довольно сложный и дискуссионный.
Здесь я постарался собрать те рекомендации, что встречал в разных источниках и что было удобно мне самому. Эти рекомендации не являются всеобъемлющими и каждая команда, как мне кажется, сама должна определять, когда что применять. Буду рад комментариям, каким рекомендациям следуете вы.
Самая главная рекомендация — не переусложняйте код, он должен быть легко читаем и однозначен. Чем сложнее код — тем больше ошибок мы можем в нем совершить. И помним, что IDEA у нас не всегда под рукой, например, часто простые исправления проверяются online, например, в gitlab, где нет таких возможностей как в IDEA.
Основные грамматические отличия можно свести в таблицу:
Функция будет принимать this
Функция будет принимать it
Будет возвращен объект на котором вызывается функция (self)
apply
also
Будет возвращен результат функции (result)
run, with
let
С различием по тому, что возвращается,как мне кажется, все понятно. Давайте внимательно рассмотрим различие: что принимает функция ( this или it ). С точки зрения возможностей — this и it полностью одинаковы, так как они предоставляют доступ к одному и тому же набору параметров. this НЕ предоставляет доступ к приватным методам. Единственное различие в том, что this может быть опущено, а it в явном виде заменено на другое имя переменной. Поэтому this рекомендуется для тех случаев, когда вызываются функции и присваиваются свойства — для настройки объектов, it — когда объект используется в основном в качестве аргумента вызова функции.
Большую часть функций удобно использовать для реализации сокращенной записи (см. ниже пример с apply)
let
- часто используется для безопасного выполнения блока кода с null-выражениями
val b: Int? = null val a = b?.let < nonNullable ->nonNullable > ?: "Equal to 'null' or not set" println(a)
also
- используется для выполнения каких-либо дополнительных действий
val numbers = mutableListOf("one", "two", "three") numbers .also < println("The list elements before adding new one: $it") >.add("four")
apply
- настроить объекта и не надо возвращать результат (удобно использовать для настройки Spring beans (бинов)
val registrar = DateTimeFormatterRegistrar().apply
run, with
- run и with очень похожи, поэтому не рекомендуется использовать их вместе.
- run — используется для настройки объекта и вычисления результата
fun printAlphabet() = StringBuilder().run < for (letter in 'A'..'Z')< append(letter) >toString() >
- run без контекстного объекта — выполнение набора операций в отдельной зоне видимости
- with — используется для объединения вызовов функций объекта
// выводим все буквы алфавита fun printAlphabet() = with(StringBuilder()) < for (letter in 'A'..'Z')< append(letter) >toString() >
Использованные материалы
- Официальная документация по функциям области видимости (перевод)
- Официальная документация по функциям расширениям (перевод)
- Статья от baeldung по функциям области видимости
- Статья о том, как запомнить что есть что на англ.
- Интересная дискусия о функциях области видимости на stackoverflow
- Рекомендации по применению функций области видимости с medium (перевод)
Функции области видимости
Стандартная библиотека Kotlin содержит несколько функций, единственной целью которых является выполнение блока кода в контексте объекта. Эти функции формируют временную область видимости для объекта, к которому были применены, и вызывают код, указанный в переданном лямбда-выражении. В этой области видимости можно получить доступ к объекту без явного к нему обращения по имени. Такие функции называются функциями области видимости (англ. scope functions). Всего их пять: let , run , with , apply , и also .
По сути, все эти функции делают одно и то же: выполняют блок кода для объекта. Отличие состоит в том, как этот объект становится доступным внутри блока и каков результат всего выражения.
Пример обычного использования функции области видимости:
data class Person(var name: String, var age: Int, var city: String) < fun moveTo(newCity: String) < city = newCity >fun incrementAge() < age++ >> fun main() < Person("Alice", 20, "Amsterdam").let < println(it) it.moveTo("London") it.incrementAge() println(it) >>
Если вы захотите написать то же самое без функции let , то вам придется объявить новую переменную и обращаться к ней всякий раз, когда она используется.
data class Person(var name: String, var age: Int, var city: String) < fun moveTo(newCity: String) < city = newCity >fun incrementAge() < age++ >> fun main()
Функции области видимости не предоставляют никаких новых технических возможностей, но они могут сделать ваш код более кратким и читабельным.
Из-за того, что все функции области видимости имеют схожий характер, выбрать правильную для вашего конкретного случая может быть затруднительно. В основном выбор зависит от ваших намерений и от структуры вашего проекта. Ниже приведено подробное описание того, чем функции области видимости отличаются между собой, а также соглашение об их использовании.
Отличительные особенности
Поскольку функции области видимости очень похожи друг на друга, важно понимать чем они различаются. Между ними есть два основных различия:
- Способ ссылки на контекстный объект
- Возвращаемое значение.
Контекстный объект: this или it
Внутри лямбда-выражения, которое передается функции области видимости, объект контекста доступен по краткой ссылке, а не по его фактическому имени. Каждая функция области видимости использует один из двух способов доступа к объекту контекста: как лямбда-получатель ( this ) или как лямбда-аргумент ( it ). Оба предоставляют одинаковые возможности, поэтому опишем плюсы и минусы каждого для разных случаев и дадим рекомендации по их использованию.
fun main() < val str = "Hello" // this str.run < println("Длина строки: $length") //println("Длина строки: $") // делает то же самое > // it str.let < println("Длина строки: $") > >
this
run , with и apply ссылаются на объект контекста как лямбда-получатель — по ключевому слову this . Следовательно, в их лямбдах объект доступен, как это было бы в обычных функциях класса. В большинстве случаев this можно опустить при доступе к элементам объекта-получателя, что сделает код короче. С другой стороны, если this опущено, то будет сложнее различить элементы-получатели с внешними объектами или функциями. Таким образом, наличие this рекомендуется для лямбд, которые в основном работают с членами объекта: вызывают его функции или присваивают свойства.
data class Person(var name: String, var age: Int = 0, var city: String = "") fun main() < val adam = Person("Adam").apply < age = 20 // то же самое, что и this.age = 20 или adam.age = 20 city = "London" >println(adam) >
it
В свою очередь, let и also передают контекстный объект как аргумент в лямбду. Если имя аргумента не указано, то к объекту обращаются неявным способом при помощи ключевого слова it . it короче this и выражения с it более читабельны. Однако при вызове функций или свойств объекта у вас не будет доступа к такому неявному объекту как this . Следовательно, использовать контекстный объект it лучше, когда объект в основном используется в качестве аргумента для вызова функций. it также лучше, если вы используете несколько переменных в блоке кода.
import kotlin.random.Random fun writeToLog(message: String) < println("INFO: $message") >fun main() < fun getRandomInt(): Int < return Random.nextInt(100).also < writeToLog("Метод getRandomInt() сгенерировал значение $it") >> val i = getRandomInt() >
Кроме того, когда вы передаете объект в качестве аргумента, вы можете указать пользовательское имя для этого объекта внутри области видимости.
import kotlin.random.Random fun writeToLog(message: String) < println("INFO: $message") >fun main() < fun getRandomInt(): Int < return Random.nextInt(100).also < value ->writeToLog("Метод getRandomInt() сгенерировал значение $value") > > val i = getRandomInt() >
Возвращаемое значение
Функции области видимости также отличаются по значению, которое они возвращают:
- apply и also возвращают объект контекста.
- let , run и with возвращают результат лямбды.
Это отличие позволит выбрать правильную функцию в зависимости от того, что вы будете делать дальше в своем коде.
Контекстный объект
Функции apply и also возвращают объект контекста. Следовательно, с их помощью можно вызвать длинную цепочку функций относительно оригинального контекстного объекта. Такая цепочка функций известна как side steps.
fun main() < val numberList = mutableListOf() numberList.also < println("Заполнение списка") >.apply < add(2.71) add(3.14) add(1.0) >.also < println("Сортировка списка") >.sort() println(numberList) >
Они также могут быть использованы совместно с ключевым словом return .
import kotlin.random.Random fun writeToLog(message: String) < println("Информация: $message") >fun main() < fun getRandomInt(): Int < return Random.nextInt(100).also < writeToLog("Метод getRandomInt() сгенерировал значение $it") >> val i = getRandomInt() >
Результат лямбды
let , run и with возвращают результат лямбды. Таким образом, вы можете использовать их при присваивании переменной результата вычислений, либо использовать результат для последующего вызова цепочки операций и тд.
fun main() < val numbers = mutableListOf("one", "two", "three") val countEndsWithE = numbers.run < add("four") add("five") count < it.endsWith("e") >> println("Элементы в $countEndsWithE, которые заканчиваются на e.") >
Кроме того, вы можете игнорировать возвращаемое значение и использовать функцию для создания временной области видимости для переменной.
fun main() < val numbers = mutableListOf("one", "two", "three") with(numbers) < val firstItem = first() val lastItem = last() println("Первый элемент: $firstItem, последний элемент: $lastItem") >>
Функции
Чтобы помочь вам выбрать правильную функцию области видимости, ниже будет представлено их подробное описание и рекомендации по использованию. Во многих случаях они взаимозаменяемы, поэтому в примерах будет отражен общий стиль использования, а также соглашение по их применению.
let
Контекстный объект доступен в качестве аргумента ( it ). Возвращаемое значение — результат выполнения лямбды.
Если значение переменной вычислялось при помощи цепочки операций, то let позволяет использовать полученный результат для вызова одной или нескольких функций в блоке кода. Например, в следующем коде выполняется цепочка из двух операций, результат записывается в отдельную переменную, после чего она выводится на печать.
fun main() < val numbers = mutableListOf("one", "two", "three", "four", "five") val resultList = numbers.map < it.length >.filter < it >3 > println(resultList) >
С функцией let этот код может быть переписан следующим образом:
fun main() < val numbers = mutableListOf("one", "two", "three", "four", "five") numbers.map < it.length >.filter < it >3 >.let < println(it) // при необходимости можно вызвать больше функций >>
Если блок кода содержит одну функцию, где it является аргументом, то лямбда-выражение может быть заменено ссылкой на метод ( :: ):
fun main() < val numbers = mutableListOf("one", "two", "three", "four", "five") numbers.map < it.length >.filter < it >3 >.let(::println) >
let часто используется для выполнения блока кода только с non-null значениями. Чтобы выполнить действия с non-null объектом, используйте оператор безопасного вызова ?. совместно с функцией let .
fun processNonNullString(str: String) <> fun main() < val str: String? = "Hello" //processNonNullString(str) // compilation error: str может быть null val length = str?.let < println("Вызов функции let() для $it") processNonNullString(it) // OK: 'it' не может быть null внутри конструкции '?.let < >' it.length > >
Еще один вариант использования let — это введение локальных переменных с ограниченной областью видимости для улучшения читабельности кода. Чтобы определить новую переменную для контекстного объекта, укажите ее имя в качестве аргумента лямбды, чтобы ее можно было использовать вместо ключевого слова it .
fun main() < val numbers = listOf("one", "two", "three", "four") val modifiedFirstItem = numbers.first().let < firstItem ->println("Первый элемент в списке: '$firstItem'") if (firstItem.length >= 5) firstItem else "!" + firstItem + "!" >.toUpperCase() println("Первый элемент списка после изменений: '$modifiedFirstItem'") >
with
Не является функцией-расширением. Контекстный объект передается в качестве аргумента, а внутри лямбда-выражения он доступен как получатель ( this ). Возвращаемое значение — результат выполнения лямбды.
Функцию with рекомендуется использовать для вызова функций контекстного объекта без предоставления результата лямбды. В коде with может читаться как» с этим объектом, сделайте следующее. «
fun main() < val numbers = mutableListOf("one", "two", "three") with(numbers) < println("'with' вызывает с аргументом $this") println("Список содержит $size элементов") >>
Другой вариант использования with — введение вспомогательного объекта, свойства или функции которые будут использоваться для вычисления значения.
fun main() < val numbers = mutableListOf("one", "two", "three") val firstAndLast = with(numbers) < "Первый элемент списка - $," + " последний элемент списка - $" > println(firstAndLast) >
run
Контекстный объект доступен в качестве получателя ( this ). Возвращаемое значение — результат выполнения лямбды.
run делает то же самое, что и with , но вызывается как let — как функция расширения контекстного объекта.
run удобен, когда лямбда содержит и инициализацию объекта, и вычисление возвращаемого значения.
class MultiportService(var url: String, var port: Int) < fun prepareRequest(): String = "Запрос по умолчанию" fun query(request: String): String = "Результат запроса: '$request'" >fun main() < val service = MultiportService("https://example.kotlinlang.org", 80) val result = service.run < port = 8080 query(prepareRequest() + " порт - $port") >// аналогичный код с использованием функции let(): val letResult = service.let < it.port = 8080 it.query(it.prepareRequest() + " порт - $") > println(result) println(letResult) >
Помимо вызова run для объекта-получателя, вы можете использовать его как функцию без расширения. В этом случае run позволяет выполнить блок из нескольких операторов там, где это требуется.
fun main() < val hexNumberRegex = run < val digits = "0-9" val hexDigits = "A-Fa-f" val sign = "+-" Regex("[$sign]?[$digits$hexDigits]+") >for (match in hexNumberRegex.findAll("+1234 -FFFF not-a-number")) < println(match.value) >>
apply
Контекстный объект доступен в качестве получателя ( this ). Возвращаемое значение — контекстный объект.
Используйте apply для такого блока кода, который не возвращает значение и в основном работает с членами объекта-получателя. Типичный способ использования функции apply — настройка объекта-получателя. Это всеравно что мы скажем “примени перечисленные настройки к объекту.”
data class Person(var name: String, var age: Int = 0, var city: String = "") fun main() < val adam = Person("Adam").apply < age = 32 city = "London" >println(adam) >
Так как возвращаемое значение — это сам объект, то можно с легкостью включить apply в цепочки вызовов для более сложной обработки.
also
Контекстный объект доступен в качестве аргумента ( it ). Возвращаемое значение — контекстный объект.
also хорош для выполнения таких действий, которые принимают контекстный объект в качестве аргумента. То есть, эту функции следует использовать, когда требуется ссылка именно на объект, а не на его свойства и функции. Либо, когда вы хотите, чтобы была доступна ссылка на this из внешней области видимости.
Когда вы видите в коде also , то это можно прочитать как «а также с объектом нужно сделать следующее.»
fun main() < val numbers = mutableListOf("one", "two", "three") numbers .also < println("Элементы списка перед добавлением нового: $it") >.add("four") >
Выбор функции
В таблице ниже приведены ключевые различия между функциями области видимости, что должно помочь вам сделать правильный выбор в пользу той или иной функции.
| Функция | Обращение к объекту | Возвращаемое значение | Является функцией-расширением |
|---|---|---|---|
| let | it | Результат лямбды | Да |
| run | this | Результат лямбды | Да |
| run | — | Результат лямбды | Нет: может быть вызвана без контекстного объекта |
| with | this | Результат лямбды | Нет: принимает контекстный объект в качестве аргумента. |
| apply | this | Контекстный объект | Да |
| also | it | Контекстный объект | Да |
Краткое руководство по выбору функции области видимости в зависимости от предполагаемого назначения:
- Выполнение лямбды для non-null объектов: let
- Представление переменной в виде выражения со своей локальной областью видимости: let
- Настройка объекта: apply
- Настройка объекта и вычисление результата: run
- Выполнение операций, для которых требуется выражение: run без расширения
- Применение дополнительных значений: also
- Группировка всех функций, вызываемых для объекта: with
Некоторые функции области видимости являются взаимозаменяемыми, поэтому вы можете выбирать функции исходя из соглашений, принятых в вашем проекте или команде.
Несмотря на то, что функции области видимости предназначены для того, чтобы сделать код более кратким, избегайте их чрезмерного использования: это может снизить читабельность кода и привести к ошибкам. Избегайте вложенности функций и будьте осторожны при их объединении: можно легко запутаться в текущем значении контекстного объекта и в значениях this или it .
takeIf и takeUnless
Помимо функций области видимости, стандартная библиотека содержит функции takeIf и takeUnless . Эти функции позволяют встроить проверку состояния объекта в цепочке вызовов.
При вызове takeIf для объекта с предикатом этот объект будет возвращен, если он соответствует предикату. В противном случае возвращается null . В свою очередь, takeUnless возвращает объект, если он не соответствует предикату, и null , если соответствует. Объект доступен как лямбда-аргумент ( it ).
import kotlin.random.* fun main() < val number = Random.nextInt(100) val evenOrNull = number.takeIf < it % 2 == 0 >val oddOrNull = number.takeUnless < it % 2 == 0 >println("четный: $evenOrNull, нечетный: $oddOrNull") >
При добавлении в цепочку вызовов других функций после takeIf и takeUnless , не забудьте выполнить проверку на null или используйте оператор безопасного вызова ( ?. ), потому что их возвращаемое значение имеет тип nullable.
fun main() < val str = "Hello" val caps = str.takeIf < it.isNotEmpty() >?.toUpperCase() //val caps = str.takeIf < it.isNotEmpty() >.toUpperCase() //compilation error println(caps) >
takeIf и takeUnless особенно полезны при совместном использовании с функциями области видимости. Хорошим примером является объединение их в цепочку с let для выполнения блока кода для объектов, которые соответствуют заданному предикату. Для этого вызовите takeIf для объекта, а затем вызовите let с оператором безопасного вызова ( ? ). Для объектов, которые не соответствуют предикату, takeIf возвращает null , а let не вызывается.
fun main() < fun displaySubstringPosition(input: String, sub: String) < input.indexOf(sub).takeIf < it >= 0 >?.let < println("Подстрока $sub находится в $input.") println("Начинается с индекса $it.") >> displaySubstringPosition("010000011", "11") displaySubstringPosition("010000011", "12") >
Тот же самый код, но без использования функций из стандартной библиотеки, выглядит следующим образом:
fun main() < fun displaySubstringPosition(input: String, sub: String) < val index = input.indexOf(sub) if (index >= 0) < println("Подстрок $sub находится в $input.") println("Начинается с индекса $index.") >> displaySubstringPosition("010000011", "11") displaySubstringPosition("010000011", "12") >