Освой Kotlin играючи

Сайт Александра Климова

Шкодим

/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000

Коллекции


Коллекции в Kotlin используют множество различных интерфейсов: Iterable, Collection, Set, List, MutableIterable, MutableCollection и др.

В Kotlin нет собственных коллекций, только Java-коллекции. Но при этом в Kotlin они обладают более широкими возможностями, используя расширения. Например, вы можете узнать последний элемент списка или найти максимальное значение в коллекции чисел.

Важная особенность - в Kotlin интерфейсы явно разделены на две группы - изменяемые и неизменяемые. Старайтесь всегда использовать неизменяемые коллекции с доступом для чтения. Если вам нужно изменять коллекцию, то тогда выбирайте другой вариант. Здесь не надо путать с var и val. Если вы создадите список для чтения и привяжете его к переменной var, список всё равно нельзя будет изменить после создания.

Для создания различных типов коллекций есть разные функции. После создания узнаем, какой класс соответствует коллекции. Основные приёмы работы с коллекциями показаны у listOf().

List

Функции для создания списков. В списках допустимы дубликаты.

listOf()

Неизменяемые списки List создаются через функцию listOf():


val list = listOf(1, 3, 9)
println(list.javaClass)
// class java.util.Arrays$ArrayList

Если у вас есть изменяемый список, то его можно сконвертировать в список для чтения через toList():


val cats = mutableListOf("Мурзик", "Барсик", "Рыжик")
val readOnlyCats = cats.toList()
println("${readOnlyCats.first()}")

Доступ к отдельному элементу списка можно получить по номеру индекса через квадратные скобки.


val cats = listOf("Мурзик", "Барсик", "Рыжик")
println(cats[2])

Также можно использовать метод get() с указанием номера индекса.


val cats = listOf("Мурзик", "Барсик", "Рыжик")
println(cats.get(2))

Но у этих способов есть один недостаток - если вы укажете неправильное значение индекса, то получите исключение ArrayIndexOutOfBoundsException. Вы можете избежать проблемы, если будете использовать метод getOrElse() с указанием значения по умолчанию, если выйдете за пределы допустимых значений. В этом случае вам не придётся обрабатывать исключение.


val cats = listOf("Мурзик", "Барсик", "Рыжик")
println(cats.getOrElse(4) { "Неизвестный котик" })

Другой вариант избежать исключения - использовать getOrNull(), который вернёт null при неправильном индексе. Осталось обработать ситуацию.


val cats = listOf("Мурзик", "Барсик", "Рыжик")
val cat = cats.getOrNull(4) ?: "Неизвестный котик"
println(cat)

Размер списка можно узнать через свойство size. Зная размер, можно без опаски пройтись по всем элементам списка.

Пройтись по всем элементам списка можно через for..in.


val cats = listOf("Cat1", "Cat2", "Cat3")
for(cat in cats) print (cat)

Умножаем каждый элемент списка на себя при помощи функции map. Функция проходит по каждому элементу, объединяя результаты в новую коллекцию.


var list = listOf(1, 2, 3)
var dest = list.map { it * it }
println(dest.toString()) // выводит [1, 4, 9]

Выводим только имена, игнорируя возраст и другие поля класса.


val cats = listOf(Cat("Barsik", 5, true),
        Cat("Murzik", 9, true))
println(cats.map (Cat::name))
// или println(cats.map {it.name})

Можно объединить вызовы функций в цепочки. Выводим имена котов, чей возраст больше 5.


val cats = listOf(Cat("Barsik", 5, true),
        Cat("Murzik", 9, true))
println(cats.filter {it.age > 5}.map (Cat::name))

С помощью any и предиката можно узнать, выполняется ли условие хотя бы для одного элемента.


val list = listOf(1, 2, 3, 4, 5)
// есть ли элемент, который делится без остатка?
println(list.any { it % 2 == 0 }) // true
// есть ли элемент больше 10?
println(list.any { it > 10 }) // false

Выражение !any можно заменить на all.

С помощью all и предиката можно узнать, выполняется ли условие для всех элементов.


val list = listOf(1, 2, 3, 4, 5)
// все элементы меньше 7?
println(list.all { it < 7 }) // true

// всем котам меньше 11 лет?
val cats = listOf(Cat("Barsik", 5, true),
        Cat("Murzik", 9, true))
println(cats.all{it.age < 11}) // true

Выражение !all можно заменить на any.

С помощью none и предиката можно узнать, что ни один из всех элементов не выполняет условие.


val list = listOf(1, 2, 3, 4, 5)
// все элементы больше 6?
println(list.none { it > 6 }) // true

count с предикатом позволяет узнать число элементов в списке, которые соответствуют условию.


val list = listOf(1, 2, 3, 4, 5)
// сколько элементов меньше 4?
println(list.count { it < 4 }) // 3

// сколько котов младше 11 лет?
val cats = listOf(Cat("Barsik", 5),
        Cat("Murzik", 9))
println(cats.count{it.age < 11}) // 2

Не используйте для этой цели цепочку filter.size, так как вы создаёте промежуточную коллекцию, а это накладные расходы. Функция count считает только количество элементов, а не сами элементы.

fold задаёт начальное значение, а потом собирает все значения в списке от первого к последнему.


val list = listOf(1, 2, 3, 4, 5)

// 15 + 1 + 2 + 3 + 4 + 5
println(list.fold(15) { total, next -> total + next }) // 30

reduce работает аналогично, только без указания начального значения.


println(listOf(1, 2, 3).reduce { total, next ->
    total + next
})

foldRight работает аналогично fold, только значения берутся от последнего к первому.


println(listOf(1, 2, 3).foldRight(1) { total, next -> total * next })

reduceRight похож на foldRight без указания начального значения.

forEach позволяет пройтись по всем элементам списка.


listOf(1, 2, 3).forEach{println(it)}

forEachIndexed работает аналогично, вдобавок мы можем получить индекс позиции и его значение.


listOf("Барсик", "Рыжик", "Васька").forEachIndexed { index, value
    ->
    println("В позиции $index содержится $value")
}

В позиции 0 содержится Барсик
В позиции 1 содержится Рыжик
В позиции 2 содержится Васька

max() позволит получить максимальное значение из списка или null при отсутствии элементов.


println(listOf(3, 9, 6).max()) // 9

Функция min() вычисляет минимальное значение из списка или null при отсутствии элементов.

maxBy() возвращает первый элемент с наибольшим значением заданной функции или null, если элементов нет.

Выберем самого старшего кота (класс содержит имя и возраст, а также другие свойства).


val cats = listOf(Cat("Барсик", 5, true),
        Cat("Мурзик", 9, true))
println(cats.maxBy { it.age })
// println(cats.maxBy (Cat::age)) // альтернативный вариант

sumBy() подсчитывает сумму всех элементов, после того, как они подверглись изменению.


// увеличиваем все элементы на 5, а потом складываем
println(listOf(1, 2, 3).sumBy { it + 5 }) // 21

// Считаем сумму длин строк
println(listOf("cat", "kitten").sumBy { it.length })

drop() отсекает указанное число элементов. Также есть функции dropWhile, dropLastWhile.


println(listOf(1, 2, 3, 4, 5).drop(2)) // [3, 4, 5]
println(listOf(1, 2, 3, 4, 5).dropWhile({ it < 3}))  // [3, 4, 5]
println(listOf(1, 2, 3, 4, 5).dropLastWhile { it > 4 })  // [1, 2, 3, 4]

take оставит первые элементы списка. takeLast оставит последние элементы.


// оставим первые два элемента
println(listOf(12, 32, 34, 45, 45).take(2)) // [12, 32]

takeWhile оставит элементы, которые соответствуют условию.


println(listOf(1, 2, 3, 4, 5).takeWhile{it < 3})

filter вернёт список, который соответствует предикату. Также есть filterNotNull, который вернёт список, не содержащий null. Функция может удалять элементы из коллекции, но не может изменять их (используйте map).


// больше или равно 3
println(listOf(1, 2, 3, 4, 5).filter { it >= 3 }) // [3, 4, 5]

// чётные числа
println(listOf(1, 2, 3, 4, 5).filter { it % 2 == 0 }) // [2, 4]

val cats = listOf("Барсик", "Мурзик", "Васька", "Рыжик")
// слова, у которых второй символ "а"
println(cats.filter { it[1] == 'а' })
// [Барсик, Васька]

// Содержит "ик", сортируем по длине слова
val cats = listOf("Барсик", "Мурзик", "Пикассо", "Васька", "Рыжик")
val filtered = cats.filter { it.contains("ик") }.sortedBy { it.length }
println(filtered)


// Начинается на "П" и оканчивается на "к"
val cats = listOf("Барсик", "Мурзик", "Пикассо", "Васька", "Рыжик", "Пушок")
val filtered = cats.filter { it.startsWith('П') } .filter { it.endsWith('к') }
println(filtered) // Пушок

filterNot вернёт список, который не соответствует предикату.


// оставляем нечётные числа
println(listOf(1, 2, 3, 4, 5).filterNot { it % 2 == 0 }) // [1, 3, 5]

contains() позволит убедиться, что нужный элемент присутствует в списке.


// содержится ли число 4 в списке
println(listOf(1, 2, 3, 4, 5).contains(4)) // true

// содержится ли в списке Барсик
val cats = listOf("Мурзик", "Барсик", "Рыжик")
println("Список содержит Барсика: ${cats.contains("Барсик")}")

Если нужно проверить сразу несколько элементов, то используйте containsAll(), передавая ему список желаемых элементов.


// содержится ли в списке Барсик и Мурзик
val cats = listOf("Мурзик", "Барсик", "Рыжик")
println("Барсик и Мурзик в списке: ${cats.containsAll(listOf("Барсик", "Мурзик"))}")

Получить элемент по индексу можно через elementAt(). Также доступны elementAtOrElse, elementAtOrNull.


println(listOf(1, 2, 3, 4, 5).elementAt(3)) // 4

first() вернёт первый элемент, соответствующий предикату. Если элемент не будет найдет, то получим исключение NoSuchElementException. Похожая функция firstOrNull вернёт null, если элемент не будет найден.


println(listOf(1, 2, 3, 4, 5).first{it % 3 == 0}) // 3

По такому же принципу работают last() и lastOrNull() для последнего элемента.

find похож на first.

indexOf() вернёт индекс элемента. Функции indexOfFirst(), indexOfLast, lastIndexOf() работают с предикатами.


println(listOf(1, 2, 3, 4, 5).indexOf(4)) // 3

Функция single() вернёт один уникальный элемент из списка. Если элементов, соответствующих условию, будет несколько будет исключение. singleOrNull вместо исключения вернёт null.


println(listOf(1, 6, 3, 4, 5).singleOrNull { it % 3 == 0 }) // null

Развернуть элементы списка можно через reversed().


val list = listOf(1, 2, 3)
println(list.reversed()) //[3, 2, 1]

Отсортировать список можно через sorted().


val list = listOf(5, 9, 1)
println(list.sorted())

В обратном порядке от большого к меньшему можно через sortedDescending().


val list = listOf(5, 9, 1)
println(list.sortedDescending())
// [9, 5, 1]

Можно сложить два списка. К элементам первого списка будут добавлены элементы второго списка.


val list1 = listOf(1, 2, 3)
val bigList = list1 + listOf(4, 5)
println(bigList)
// [1, 2, 3, 4, 5]

Добавить ещё один элемент можно через plus(). Оригинальный список останется без изменений и будет создан новый список.


val list = listOf(1, 2, 3, 4, 5)
println(list.plus(6)) // новый список [1, 2, 3, 4, 5, 6]
println(list) // старый список [1, 2, 3, 4, 5]

Функция minus() отнимает указанный элемент.


val list = listOf(1, 2, 3, 4, 5)
println(list.minus(3)) // [1, 2, 4, 5]

Функция average() подсчитывает среднее значение всех элементов списка.


val list = listOf(1, 2, 3, 4, 5)
println(list.average()) // 3.0

Функция slice возвращает список по указанным индексам.


println(listOf(1, 2, 3, 4, 5).slice(listOf(1, 3, 4)))  // [2, 4, 5]

Функция shuffled() создаёт новый список с перемешанными элементами от старого списка.


val items = listOf(1, 2, 3)
val newList = items.shuffled()

println("shuffled: $newList") // shuffled: [2, 1, 3] один из вариантов

Разбить список на отдельные элементы с определённым условием можно через функцию partition. При значении true элементы попадают в одну группу, а остальные в другую. Возвращается объект Pair, содержащий два списка.


// по признаку чётности
val listA = listOf(1, 2, 3, 4, 5, 6)
val pair = listA.partition {
    it % 2 == 0
}
println(pair)
// ([2, 4, 6], [1, 3, 5])

groupBy группирует значения по некоторому критерию. Например, мы хотим разбить список котов по возрасту.


val cats = listOf(Cat("Barsik", 5),
        Cat("Murzik", 9), Cat("Ryzhik", 5))
println(cats.groupBy { it.age })

// Результат
{5=[Cat(name=Barsik, age=5), Cat(name=Ryzhik, age=5)],
 9=[Cat(name=Murzik, age=9)]}

На выходе получается словарь с ключами (Map<Int, List<Cat>>), которые определяют признак группировки (в нашем случае это возраст). Мы получили одну группу котов с возрастом 5 лет и вторую группу с возрастом 9 лет. Вы можете изменить словарь при помощи mapKeys и mapValues.

Список может содержать дубликаты. Но если вы хотите избавиться от них, то можно преобразовать список во множество, а затем снова в список. Существует готовая функция distinct(), которая сделает это за вас.


val cats = listOf("Рыжик", "Мурзик", "Барсик", "Рыжик")
        .distinct()

emptyList()

Пустой неизменяемый список можно создать через emptyList(), который вернёт тип List.


val emptyList: List<String> = emptyList<String>()

listOfNotNull()

Ещё один вид неизменяемых списков - listOfNotNull().


val nonNullsList: List<String> = listOfNotNull(9, 35, 7, null, 44, null)

arrayListOf()

Изменяемый список создаётся через arrayListOf(), который возвращает ArrayList.


//val stringList: ArrayList<String> = arrayListOf<String>("Hello", "Kitty")
val stringList = arrayListOf("Hello", "Kitty")
println(stringList::class.java) // class java.util.ArrayList

Сделаем обход коллекции с использованием индекса.


val list = arrayListOf("3", "8", "4", "1", "9")
for ((index, element) in list.withIndex()){
    println("$index: $element")
}

// Результат
0: 3
1: 8
2: 4
3: 1
4: 9

mutableListOf()

Изменять данные можно только в изменяемых списках. Но если у вас есть в наличии неизменяемый список, то его можно сконвертировать в изменяемый через специальный метод toMutableList(). При этом будет создан новый список.


var mutableNames = names.toMutableList() // превращаем в изменяемый список
mutableNames.add("Рыжик") // добавляем новое имя
println(mutableNames::class.java)
println(mutableNames[3])

А можно сразу создать изменяемый список нужного типа через mutableListOf<T>(). Также есть перегруженная версия без указания нужного типа - mutableListOf().


val mutableListNames: MutableList<String> =
        mutableListOf<String>("Барсик", "Мурзик", "Васька")
mutableListNames.add("Рыжик") // добавляем
mutableListNames.removeAt(1) // удаляем второй элемент
mutableListNames[0] = "Пушок" // заменяем первый элемент через присваивание

// изменяемый список из разных типов
val mutableListMixed = mutableListOf("Кот", "Собака", 5, 5.27, 'F')

С помощью addAll() можно добавить в список элементы другого списка.


var listA = mutableListOf("a", "a", "b")
var listB = mutableListOf("a", "c", "d")
listB.addAll(listA)
println(listB) // [a, c, d, a, a, b]

Удалить элемент из изменяемого списка можно через removeIf(), указав лямбда-выражение в качестве условия.


val cats = mutableListOf("Мурзик", "Барсик", "Рыжик")
cats.removeIf{it.contains("у")}
println(cats)

// выводится: [Барсик, Рыжик]

Другой способ объединения двух списков с сохранением только уникальных элементов. Обратите внимание на порядок сохранения - сначала берутся элементы основного списка, а затем добавляются элементы добавляемого списка.


val listA = mutableListOf("a", "e", "b")
val listB = mutableListOf("a", "c", "d", "e", "f")
val listC = listB.union(listA)
println(listC)

shuffle() (Kotlin 1.2)

Перемешивает элементы в случайном порядке.


val items = (1..5).toMutableList()

items.shuffle()
println("Shuffled items: $items")
// [2, 3, 1, 4, 5] один из вариантов

replaceAll (Kotlin 1.2)

Заменяет каждый элемент списка новым значением в результате заданного выражения. Доступно для API 24 и выше


val items = (1..5).toMutableList()

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
    items.replaceAll { it * 2 }
}
println("Значения удвоены: $items") // [2, 4, 6, 8, 10]

fill (Kotlin 1.2)

Заменяет все элементы списка заданным значением.


val items = (1..5).toMutableList()

println("Было: $items")
items.fill(5)
println("Стало: $items")

//Было: [1, 2, 3, 4, 5]
//Стало: [5, 5, 5, 5, 5]

Все коллекция являются стандартными и полностью совместимыми с Java, Kotlin не использует собственных коллекций, но тем не менее предлагает удобные методы из коробки. Выше были показаны примеры для списков. Можно применять методы для других коллекций.

Например, найдём последний элемент из коллекции.


val set = setOf("Васька", "Мурзик", "Барсик")
println(set.last())

joinToString()

Встроенная котлиновская функция для создания строки из элементов коллекции. Вы можете задать собственный префикс и постфикс, а также разделитель. При большом количестве элементов можно задать лимит, тогда будут показаны первые элементы коллекции и после них многоточие.

Функция использует параметры по умолчанию, поэтому часть параметров можно опускать.


val numbers = listOf(1, 2, 3, 4, 5, 6)
println(numbers.joinToString()) // 1, 2, 3, 4, 5, 6
println(numbers.joinToString(prefix = "[", postfix = "]")) // [1, 2, 3, 4, 5, 6]
println(numbers.joinToString(prefix = "<", postfix = ">", separator = "•")) // <1•2•3•4•5•6>

val chars = charArrayOf('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q')
println(chars.joinToString(limit = 5, truncated = "...!") { it.toUpperCase().toString() }) // A, B, C, D, E, ...!

Создать список из случайных чисел

В Kotlin можно создать функцию для генерации списка из случайных чисел очень лаконичным способом.


private val random = Random()
private fun buildRandom() = List(25) { random.nextInt() }

// вызываем функцию
println(buildRandom())
Реклама