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
В функции дважды используется переменная с одним именем, но они не мешают друг другу и выводят разные сообщения, не переопределяя своих значений.
Это был простейший и достаточно бесполезный пример. В реальности он используется немного иначе.
Защита от null, ключевое слово let
В Java существует проблема с исключением NullPointerException и разработчику нужно постоянно устраивать проверки на null. В Kotlin существует защита от подобных ошибок. Нужно использовать вопросительный знак ? у типа.
// Не скомпилируется. Кот не может быть null var notNullCat: Cat = null // Кот может быть null var cat: Cat? = null // Не скомпилируется, кот может быть null и мы должны учитывать это cat.print() // Можем печатать, если cat != null cat?.print() // Умное приведение. Если мы проводили проверку if, // то можно вызывать обычным способом // checked nullity if (cat != null) < cat.print() >// Если уверены, что объект не null. Иначе выбросит исключение cat. print() // Используйте элвис-оператор, чтобы дать альтернативное значение, если объект равен null val name = cat?.name ?: "empty"
Любой объект может быть null и мы можем явно указать это через символ вопроса ?. В этом случае Kotlin будет проверять на возможность ошибки и предупреждать на этапе разработки.
Любой тип, не поддерживающий null, является дочерним типом соответствующего типа, поддерживающего null, поэтому не путайте следующие выражения:
// Так можно val x: Int = 9 val y: Int? = x // Так нельзя val x: Int? = 9 val y: Int = x
Умное приведение Smart cast позволяет превратить переменную из одного состояния в другое: cat? превратится в cat (см. пример выше) или переменная a типа Int? превратится в Int.
val a: Int? = null if (a != null) < a.toLong() // теперь это Int >
Если умное приведение вам не нужно, то используйте запись с безопасным оператором ?.
val a: Int? = null a?.toLong()
Функция будет вызвана только в том случае, если значение a отлично от null. Безопасные вызовы можно сцеплять.
Не путайте null с пустой строкой «». Выводим значение переменной типа String.
private var catName: String? = null println(catName) // Выводится следующее null
Любую функцию или параметр конструктора можно объявить с null-совместимым типом. Следующий код определяет функцию printInt(), которая получает параметр типа Int? (null-совместимый Int):
fun printInt(x: Int?)
Функция может иметь null-совместимый возвращаемый тип.
fun result(): Long?
Можно создать массив нулевых объектов. При этом массив может содержать и строки и null.
var cats: Array = arrayOf("Васька", "Мурзик", null)
Элвис-оператор ?:
Другой «Элвис-оператор» ?: (напоминает причёску Элвиса Пресли, если повернуть голову, как на смайликах) позволяет назначить альтернативное значение, если объект равен null.
val a: Int? = null val myLong = a?.toLong() ?: 0L var cat: Cat? = null // Если null, то вернуть "Без имени" val string: String = cat?.name ?: "Без имени" println(string)
Справа от элвис-оператора можно использовать return и выбрасывать исключения.
val myLong = a?.toLong() ?: return false val myLong = a?.toLong() ?: throw IllegalStateException()
Оператор !!
Если вы точно уверены, что ваша переменная не null, то можете использовать оператор !!. Kotlin будет полагаться на ваш профессионализм и не станет проверять ваше предположение. Если вы ошиблись в своём предположении, то ваше приложение может грохнуться.
// скомпилируется, но приложение грохнется val a: Int? = null a. toLong()
Например, в Android часто объявляются компоненты, а инициализация происходит позже.
// объявляем var button: Button? = null override fun onCreate(savedInstanceState: Bundle?) < super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // инициализируем button = findViewById(R.id.button) button?.setOnClickListener(View.OnClickListener < >) >
Подобный способ позволяет избежать null и аналогичен записи в Java.
if (button != null) < button.setOnClickListener(/* Ваш код */); >
При полной уверенности можете написать
button. setOnClickListener < /* */ >
Ключевое слово let
Ещё один способ избежать проблем с null — это использовать ключевое слово let вместе с оператором ?.:
cat?.let
Код будет выводить имя кота только в том случае, если объект не равен null.
Этот подход удобен, когда имеются смешанные варианты и мы хотим выполнения кода только для объектов, которые не имеют значения null:
var array = arrayOf("Мурзик", "Васька", null) for (item in array) < item?.let< println(it) >>
В массиве три элемента, но на экран выводятся только два элемента.
Такой подход сокращает и упрощает код. Допустим, у нас есть функция getBestCat(), возвращающий тип Cat?.
fun getBestCat(): Cat?
Можем вызвать с проверкой на null.
var cat = getBestCat() if(cat != null)
А можно обойтись без создания новой переменной, а сразу использовать let:
getBestCat()?.let
Запись означает следующее: получить объект «Самый лучший кот», и если объект не null, то позволить ему есть (eat()).
Let kotlin что это
Scope-функции (можно перевести как «функции контекста» или «функции области видимости») позволяют выполнить для некоторого объекта некоторый код в виде лямбда-выражение. При вызове подобной функции, создается временная область видимости. В этой области видимости можно обращаться к объекту без использования его имени.
В Kotlin есть пять подобных функций: let , run , with , apply и also . Эти функции похожи по своему действию и различаются прежде всего по параметрам и возвращаемым результатам
let
Лямбда-выражение в функции let в качестве параметра it получает объект, для которого вызывается функция. Возвращаемый результат функции let представляет результат данного лямбда-выражения.
inline fun T.let(block: (T) -> R): R
Распространенным сценарием, где применяется данная функция, представляет проверка на null:
fun main() < val sam = Person("Sam", "sam@gmail.com") sam.email?.let< println("Email: $it") >// Email: sam@gmail.com // аналог без функции let //if(sam.email!=null) println("Email:$") val tom = Person("Tom", null) tom.email?.let < println("Email: $it") >// функция let не будет выполняться > data class Person(val name: String, val email: String?)
Допустим, мы хотим вывести значение свойства Email объекта Person. Но это свойство может хранить значение null (например, если электронный адрес у пользователя не установлен). С помощью выражения sam.email?. проверяем значение свойства email на null . Если email не равно null, то для строки в свойстве email вызывается функция let , которая создает временную область видимости и передает в нее значение свойства email через параметр it . Если же свойство email равно null, то функция let не вызывается.
Если лямбда-выражение вызывает лишь одну функцию, в которую передается параметр it, то можно сократить вызов — указать после оператора :: название вызываемой функции:
fun main() < val sam = Person("Sam", "sam@gmail.com") sam.email?.let(::println) // sam@gmail.com val tom = Person("Tom", "tom@gmail.com") tom.email?.let(::printEmail) // Email: tom@gmail.com >fun printEmail(email: String) < println("Email: $email") >data class Person(val name: String, var email: String?)
with
Лямбда-выражение в функции with в качестве параметра this получает объект, для которого вызывается функция. Возвращаемый результат функции with представляет результат данного лямбда-выражения.
inline fun with(receiver: T, block: T.() -> R): R
Функция with принимает объект, для которого надо выполнить блок кода, в качестве параметра. Далее в самом блоке кода мы можем обращаться к общедоступным свойствам и методам объекта без его имени.
Обычно функция with() применяется, когда надо выполнить над объектом набор операций как единое целое:
fun main() < val tom = Person("Tom", null) val emailOfTom = with(tom) < if(email==null) email = "$@gmail.com" email > println(emailOfTom) // tom@gmail.com > data class Person(val name: String, var email: String?)
В данном случае функция with получает объект tom, поверяет его свойство email — если оно равно null, то устанавливает его на основе его имени. В качестве результата функции возвращается значение свойства email.
run
Лямбда-выражение в функции run в качестве параметра this получает объект, для которого вызывается функция. Возвращаемый результат функции run представляет результат данного лямбда-выражения.
inline fun T.run(block: T.() -> R): R
Функция run похожа на функцию with за тем исключением, что run реализована как функция расширения. Функция run также принимает объект, для которого надо выполнить блок кода, в качестве параметра. Далее в самом блоке кода мы можем обращаться к общедоступным свойствам и методам объекта без его имени.
fun main() < val tom = Person("Tom", null) val emailOfTom = tom.run < if(email==null) email = "$@gmail.com" email > println(emailOfTom) // tom@gmail.com > data class Person(val name: String, var email: String?)
В данном случае функция run выполняет действия, аналогичные примеру с функцией with .
Реализация run как функции расширения упрощает проверку на null:
fun main() < val tom = Person("Tom", null) val validationResult = tom.email?.run ?: "undefined" println(validationResult) // undefined > data class Person(val name: String, var email: String?)
Также есть другая разновидность функции run() , которая просто позволяет выполнить некоторое лямбда-выражение:
fun main() < val randomText = run< "hello world">println(randomText) // hello world run < println("run function")>// run function >
apply
Лямбда-выражение в функции apply в качестве параметра this получает объект, для которого вызывается функция. Возвращаемым результатом функции фактически является передаваемый в функцию объект для которого выполняется функция.
inline fun T.apply(block: T.() -> Unit): T
fun main() < val tom = Person("Tom", null) tom.apply < if(email==null) email = "$@gmail.com" > println(tom.email) // tom@gmail.com > data class Person(val name: String, var email: String?)
В данном случае, как и в примерах с функциями with и run , проверяем значение свойства email, и если оно равно null, устанавливаем его, используя свойство name.
Распространенным сценарием применения функции apply() является построение объекта в виде реализации вариации паттерна «Строитель»:
fun main() < val bob = Employee() bob.name("Bob") bob.age(26) bob.company("JetBrains") println("$($) - $") // Bob (26) - JetBrains > data class Employee(var name: String = "", var age: Int = 0, var company: String = "") < fun age(_age: Int): Employee = apply < age = _age >fun name(_name: String): Employee = apply < name = _name >fun company(_company: String): Employee = apply < company = _company >>
В данном случае класс Employee содержит три метода, которые устанавливают каждое из свойств класса. Причем каждый подобный метод вызывает функцию apply() , которое передает значение соответствующему свойству и возвращает текущий объект Employee.
also
Лямбда-выражение в функции also в качестве параметра it получает объект, для которого вызывается функция. Возвращаемым результатом функции фактически является передаваемый в функцию объект для которого выполняется функция.
inline fun T.also(block: (T) -> Unit): T
Эта функция аналогична функции apply за тем исключением, что внутри also объект, над которым выполняется блок кода, доступен через параметр it :
fun main() < val tom = Person("Tom", null) tom.also < if(it.email==null) it.email = "$@gmail.com" > println(tom.email) // tom@gmail.com > data class Person(val name: String, var email: String?)
Функции области видимости
Стандартная библиотека 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") >