Cтандартные функции
apply
apply можно использовать, чтобы уменьшить количество повторений при подготовке объекта к использованию.
val menuFile = File("menu-file.txt") menuFile.setReadable(true) menuFile.setWritable(true) menuFile.setExecutable(false)
Используя apply, то же самое можно реализовать меньшим количеством кода:
val menuFile = File("menu-file.txt").apply { setReadable(true) setWritable(true) setExecutable(false) }
Такое поведение иногда называют ограничением относительной области видимости (relative scoping), потому что вызовы всех функций внутри лямбды относятся к объекту-приемнику
let
определяет переменную в область видимости заданной лямбды и позволяет использовать ключевое слово it для ссылки на нее
val firstItemSquared = listOf(1,2,3).first().let { it * it }
Без let пришлось бы присвоить первый элемент переменной, чтобы выполнить умножение:
val firstElement = listOf(1,2,3).first() val firstItemSquared = firstElement * firstElement
Взгляните на следующий пример, который изменяет текст приветствия в зависимости от того, узнал трактирщик игрока или нет
fun formatGreeting(vipGuest: String?): String { return vipGuest?.let { "Welcome, $it. Please, go straight back - your table is ready." } ?: "Welcome to the tavern. You'll be seated soon." }
run
Функция run похожа на apply, точно так же ограничивая относительную область видимости, но не возвращает объект-приемник.
Например, вот как можно проверить наличие конкретной строки в файле:
val menuFile = File("menu-file.txt") val servesDragonsBreath = menuFile.run { readText().contains("Dragon's Breath") }
run может также использоваться для выполнения ссылки на функцию относительно объекта-приемника. Вы использовали ссылки на функции в главе 5: вот пример, как сделать это с run:
fun nameIsLong(name: String) = name.length >= 20 "Madrigal".run(::nameIsLong) // Ложь "Polarcubis, Supreme Master of NyetHack".run(::nameIsLong) // Истина
Выгоды от использования run становятся более очевидными, когда требуется вызвать несколько функций: вызов цепочкой с помощью run проще читать и анализировать.
fun nameIsLong(name: String) = name.length >= 20 fun playerCreateMessage(nameTooLong: Boolean): String { return if (nameTooLong) { "Name is too long. Please choose another name."} else { "Welcome, adventurer" } } "Polarcubis, Supreme Master of NyetHack" .run(::nameIsLong) .run(::playerCreateMessage) .run(::println)
with
with — это разновидность run. Она ведет себя похожим образом, но использует другие соглашения вызова. В отличие от стандартных функций, рассмотренных ранее, with требует, чтобы объект-приемник передавался ей в первом аргументе, а не как субъект вызова, как это принято в других стандартных функциях:
val nameTooLong = with("Polarcubis, Supreme Master of NyetHack") { length >= 20 }
Более того, рекомендуем избегать with и использовать вместо нее run.
val list = mutableListOf<Int>() with(list) { for(i in 0 until 1000) { add((Math.random() * 100).toInt()) } val sum = sum() val average = average() val max = maxOrNull() val min = minOrNull() val first = first() val last = last() println("$sum\n$average\n$max\n$min\n$first\n$last") }
also
Функция also похожа на функцию let. Как и let, also передает объект-приемник как аргумент в лямбду. Но есть одно большое различие между let и also: вторая возвращает объект-приемник, а не результат лямбды.
Это делает also особенно полезной для добавления различных побочных эффектов. Пример ниже дважды вызывает also для выполнения двух разных операций: первая выводит имя файла, а вторая записывает содержимое файла в переменную fileContents.
var fileContents: List<String> File("file.txt") .also { print(it.name) }.also { fileContents = it.readLines() } }
Так как also возвращает объект-приемник, а не результат лямбды, с ее помощью можно вызвать длинную цепочку функций относительно оригинального объекта-приемника.
filter
===============================1======================== Отфильтрует числа, которые делятся на 3 val list = listOf(1,2,3,4,5,6,7) list.filter{ it % 3 == 0 } ===============================2======================== Отфильтрует числа с элементами null val nullableList : List<Int?> = listOf(1, 2, 5, 8, null, 9, 12) println(nullableList.filter { x -> x != null && x % 3 == 0 }) ===============================3======================== Фильтр по возрасту println(people.filter { it.age > 30 }) val people = listOf( Person("Alice", 29), Person("Bob", 30), Person("Carol", 31) ) ===============================4======================== Фильтр по возрасту val people = listOf( Person("Alice", 29), Person("Bob", 30), Person("Carol", 31) ) println(people.filter { it.age == 60 }) - ошибка! элемента такого нет people.firstOrNull { it.age == 60 } - вернет null если ничего не найдёт
any
Позволяет проверить соответствует ли заданному критерию элемент из коллекции. Если соответствует, то результат да.
val people = listOf( Person("Alice", 29), Person("Bob", 30), Person("Carol", 31) ) people.any{it.age == 30} //true
count
Посчитает количество объектов в списке people.count() Можно через предикат people.count{it.age > 30} посчитаем кол-во элементов с заданным условием
find
Выведет на экран элемент, который соответсвует данному условию. people.find { it.age == 31 && it.name.contains('o') }
groupBy
Группировка по возрасту. people.groupBy { it.age } Результат: { 30=[Person(name=Alice, age=30), Person(name=Bob, age=30)], 60=[Person(name=Buba, age=60)], 31=[Person(name=Carol, age=31)] }
Возвращает мапу, можно вывести значения
people.groupBy { it.age }.keys
associateBy
То же что и groupBy только сохраняется один элемент в каждом возрасте (последний) а не все.
people.associateBy { it.age }
partition
Создает коллекции, которые соответствуют условию или не соответствуют.
people.partition { it.age > 30 } Результат: ( [Person(name=Buba, age=60), Person(name=Carol, age=31)], [Person(name=Alice, age=30), Person(name=Bob, age=30)] )