Обзор коллекций Kotlin
Библиотека стандартных функций Kotlin предоставляет полный набор инструментов для управления коллекциями — группами переменного количества элементов (возможно, нулевого), которые объединены по смыслу решаемой задачи и часто обрабатываются вместе.
Коллекции — это распространённое понятие для большинства языков программирования, поэтому если вы знакомы, например, с коллекциями Java или Python, можете пропустить это введение и перейти к детальным разделам.
Коллекция обычно содержит несколько объектов (это число также может быть нулевым) одного типа. Объекты в коллекции называются элементами или записями. Например, все студенты в отделении образуют коллекцию, которую можно использовать для расчёта их среднего возраста. Для Kotlin следующие типы коллекций являются актуальными:
- Список — упорядоченная коллекция с доступом к элементам по индексам — целым числам, которые отражают их позицию. Элементы могут повторяться в списке. Примером списка является предложение: это группа слов, их порядок важен, и они могут повторяться.
- Множество — коллекция уникальных элементов. Оно отражает математическую абстракцию множества: группа объектов без повторений. В целом, порядок элементов множества не имеет значения. Например, алфавит — это множество букв.
- Карта (или словарь) — множество пар ключ-значение. Ключи уникальны, и каждый из них сопоставлен ровно с одним значением. Значения могут дублироваться. Карты полезны для хранения логических связей между объектами, например, идентификатора сотрудника и его должности.
Kotlin позволяет манипулировать коллекциями независимо от точного типа объектов, хранящихся в них. Другими словами, вы добавляете String в список String так же, как и с Int или пользовательским классом. Таким образом, библиотека стандартных функций Kotlin предлагает обобщённые интерфейсы, классы и функции для создания, заполнения и управления коллекциями любого типа.
Интерфейсы коллекций и связанные с ними функции находятся в пакете kotlin.collections. Давайте рассмотрим содержимое этого пакета.
Типы коллекций
Библиотека стандартных функций Kotlin предоставляет реализации для основных типов коллекций: множеств, списков и карт. Каждый тип коллекции представлен парой интерфейсов:
- Только для чтения интерфейс, предоставляющий операции для доступа к элементам коллекции.
- Изменяемый интерфейс, расширяющий соответствующий интерфейс только для чтения, с операциями записи: добавлением, удалением и обновлением элементов.
Обратите внимание, что изменение изменяемой коллекции не требует, чтобы она была var: операции записи изменяют тот же объект изменяемой коллекции, поэтому ссылка не изменяется. Однако, если вы попытаетесь переназначить val коллекцию, получите ошибку компиляции.
fun main() {
//sampleStart
val numbers = mutableListOf("one", "two", "three", "four")
numbers.add("five") // this is OK
//numbers = mutableListOf("six", "seven") // compilation error
//sampleEnd
}
Типы коллекций только для чтения являются ковариантными. Это означает, что если Rectangle класс наследует от Shape, вы можете использовать List<Rectangle> там, где требуется List<Shape>. Другими словами, типы коллекций имеют те же отношения подтипизации, что и типы элементов. Карты ковариантны по типу значения, но не по типу ключа.
В свою очередь, изменяемые коллекции не являются ковариантными; в противном случае это приведёт к ошибкам во время выполнения. Если MutableList<Rectangle> был бы подтипом MutableList<Shape>, вы могли бы вставить другие Shape наследники (например, Circle) в него, тем самым нарушая его Rectangle тип аргумента.
Ниже приведена диаграмма интерфейсов коллекций Kotlin:
Давайте пройдёмся по интерфейсам и их реализациям.
Коллекция
Collection<T> — корень иерархии коллекций. Этот интерфейс представляет общее поведение коллекции только для чтения: получение размера, проверку принадлежности элемента и так далее. Collection наследует от интерфейса Iterable<T>, который определяет операции для итерирования элементов. Вы можете использовать Collection в качестве параметра функции, которая применяется к различным типам коллекций. Для более специфических случаев используйте наследников Collection: List и Set.
fun printAll(strings: Collection<String>) {
for(s in strings) print("$s ")
println()
}
fun main() {
val stringList = listOf("one", "two", "one")
printAll(stringList)
val stringSet = setOf("one", "two", "three")
printAll(stringSet)
}
MutableCollection — это Collection с операциями записи, такими как add и remove.
fun List<String>.getShortWordsTo(shortWords: MutableList<String>, maxLength: Int) {
this.filterTo(shortWords) { it.length <= maxLength }
// throwing away the articles
val articles = setOf("a", "A", "an", "An", "the", "The")
shortWords -= articles
}
fun main() {
val words = "A long time ago in a galaxy far far away".split(" ")
val shortWords = mutableListOf<String>()
words.getShortWordsTo(shortWords, 3)
println(shortWords)
}
Список
List<T> хранит элементы в заданном порядке и предоставляет индексированный доступ к ним. Индексы начинаются с нуля — индекс первого элемента — и идут до lastIndex, который является (list.size - 1).
fun main() {
//sampleStart
val numbers = listOf("one", "two", "three", "four")
println("Number of elements: ${numbers.size}")
println("Third element: ${numbers.get(2)}")
println("Fourth element: ${numbers[3]}")
println("Index of element \"two\" ${numbers.indexOf("two")}")
//sampleEnd
}
Элементы списка (включая null) могут дублироваться: список может содержать любое количество одинаковых объектов или вхождений одного объекта. Два списка считаются равными, если у них одинаковый размер и структурно равные элементы в одинаковых позициях.
data class Person(var name: String, var age: Int)
fun main() {
//sampleStart
val bob = Person("Bob", 31)
val people = listOf(Person("Adam", 20), bob, bob)
val people2 = listOf(Person("Adam", 20), Person("Bob", 31), bob)
println(people == people2)
bob.age = 32
println(people == people2)
//sampleEnd
}
MutableList<T> — это List со специфическими для списка операциями записи, например, для добавления или удаления элемента в определённой позиции.
fun main() {
//sampleStart
val numbers = mutableListOf(1, 2, 3, 4)
numbers.add(5)
numbers.removeAt(1)
numbers[0] = 0
numbers.shuffle()
println(numbers)
//sampleEnd
}
Как видите, в некоторых аспектах списки очень похожи на массивы. Однако есть одно важное различие: размер массива определяется при инициализации и никогда не меняется; в свою очередь, у списка нет предопределённого размера; размер списка может меняться в результате операций записи: добавления, обновления или удаления элементов.
В Kotlin, по умолчанию, реализацией List является ArrayList, которую можно рассматривать как изменяемый массив.
Множество
Set<T> хранит уникальные элементы; их порядок, как правило, не определён. null элементы также уникальны: Set может содержать только один null . Два множества равны, если у них одинаковый размер, и для каждого элемента одного множества существует равный элемент в другом множестве.
fun main() {
//sampleStart
val numbers = setOf(1, 2, 3, 4)
println("Number of elements: ${numbers.size}")
if (numbers.contains(1)) println("1 is in the set")
val numbersBackwards = setOf(4, 3, 2, 1)
println("The sets are equal: ${numbers == numbersBackwards}")
//sampleEnd
}
MutableSet — это Set с операциями записи из MutableCollection.
По умолчанию, реализация Set — LinkedHashSet — сохраняет порядок вставки элементов. Следовательно, функции, которые зависят от порядка, такие как first() или last(), возвращают предсказуемые результаты на таких множествах.
fun main() {
//sampleStart
val numbers = setOf(1, 2, 3, 4) // LinkedHashSet is the default implementation
val numbersBackwards = setOf(4, 3, 2, 1)
println(numbers.first() == numbersBackwards.first())
println(numbers.first() == numbersBackwards.last())
//sampleEnd
}
Альтернативная реализация — HashSet — ничего не говорит о порядке элементов, поэтому вызов таких функций возвращает непредсказуемые результаты. Однако, HashSet требует меньше памяти для хранения того же количества элементов.
Карта
Map<K, V> не является наследником интерфейса Collection, однако это также тип коллекции Kotlin. Map хранит пары ключ-значение (или записи); ключи уникальны, но разные ключи могут быть связаны с равными значениями. Map интерфейс предоставляет специальные функции, такие как доступ к значению по ключу, поиск ключей и значений и так далее.
fun main() {
//sampleStart
val numbersMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key4" to 1)
println("All keys: ${numbersMap.keys}")
println("All values: ${numbersMap.values}")
if ("key2" in numbersMap) println("Value by key \"key2\": ${numbersMap["key2"]}")
if (1 in numbersMap.values) println("The value 1 is in the map")
if (numbersMap.containsValue(1)) println("The value 1 is in the map") // same as previous
//sampleEnd
}
Две карты, содержащие равные пары, равны независимо от порядка пар.
fun main() {
//sampleStart
val numbersMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key4" to 1)
val anotherMap = mapOf("key2" to 2, "key1" to 1, "key4" to 1, "key3" to 3)
println("The maps are equal: ${numbersMap == anotherMap}")
//sampleEnd
}
MutableMap — это Map с операциями записи карты, например, вы можете добавить новую пару ключ-значение или обновить значение, связанное с заданным ключом.
fun main() {
//sampleStart
val numbersMap = mutableMapOf("one" to 1, "two" to 2)
numbersMap.put("three", 3)
numbersMap["one"] = 11
println(numbersMap)
//sampleEnd
}
По умолчанию, реализация Map — LinkedHashMap — сохраняет порядок вставки элементов при итерировании карты. В свою очередь, альтернативная реализация — HashMap — ничего не говорит о порядке элементов.
© 2010–2020 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/reference/collections-overview.html