Перейти к содержимому

Let kotlin что это

  • автор:

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") > 

Добавить комментарий

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