Коллекции

В отличае от многих языков, Kotlin различает изменяемые и неизменяемые коллекции (списки, множества, ассоциативные списки и т.д.). Точный контроль над тем, когда именно коллекции могут быть изменены, полезен для устранения багов и разработки хорошего API.

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

Тип List<out T> в Kotlin — интерфейс, который предоставляет read-only операции, такие как size, get, и другие. Так же, как и в Java, он наследуется от Collection<T>, а значит и от Iterable<T>. Методы, которые изменяют список добавлены в интерфейс MutableList<T>. Тоже самое относится и к Set<out T>/MutableSet<T>, Map<K, out V>/MutableMap<K, V>.

Пример базового использования списка (list) и множества (set):

val numbers: MutableList<Int> = mutableListOf(1, 2, 3)
val readOnlyView: List<Int> = numbers
println(numbers)        // выведет "[1, 2, 3]"
numbers.add(4)
println(readOnlyView)   // выведет "[1, 2, 3, 4]"
readOnlyView.clear()    // -> не скомпилируется

val strings = hashSetOf("a", "b", "c", "c")
assert(strings.size == 3)

Kotlin не имеет специальных синтаксических конструкций для создания списков или множеств. Используйте методы из стандартной библиотеки, такие как listOf(), mutableListOf(), setOf(), mutableSetOf(). Создание ассоциативного списка некритичным для производительности способом может быть осуществленно с помощью простой идиомы: mapOf(a to b, c to d)

Заметьте, что переменная readOnlyView изменяется вместе со списком, на который она указывает. Если единственная ссылка, указывающая на список является read-only, то мы можем с уверенностью сказать, что коллекция полностью неизменяемая. Простой способ создания такой коллекции выглядит так:

val items = listOf(1, 2, 3)

В данный момент, метод listOf реализован с помощью ArrayList, но не исключено, что в будущем могут быть использованы другие типы коллекций, более эффективные по памяти за счёт своей неизменности.

Заметьте, что read-only типы ковариантны. Это значит, что вы можете взять List<Rectangle> (список прямоугольников) и присвоить его List<Shape> (списку фигур) предполагая, что Rectangle наследуется от Shape. Такое присвоение было бы запрещено с изменяемыми коллекциями, потому что в таком случае появляется риск возникновения ошибок времени исполнения.

Иногда вам необходимо вернуть состояние коллекции в определённый момент времени:

class Controller {
    private val _items = mutableListOf<String>()
    val items: List<String> get() = _items.toList()
}

Расширение toList просто копирует элементы списка. Таким образом, возвращаемый список гарантированно не изменится.

Существует несколько полезных расширений для списков и множеств, с которыми стоит познакомиться:

val items = listOf(1, 2, 3, 4)
items.first() == 1
items.last() == 4
items.filter { it % 2 == 0 }   // возвратит [2, 4]

val rwList = mutableListOf(1, 2, 3)
rwList.requireNoNulls()        // возвратит [1, 2, 3]
if (rwList.none { it > 6 }) println("Нет элементов больше 6")  // выведет "Нет элементов больше 6"
val item = rwList.firstOrNull()

Также, обратите внимание на такие утилиты как sort, zip, fold, reduce.

Тоже самое происходит и с ассоциативными списками. Они могут быть с лёгкостью инициализированы и использованы следующим образом:

val readWriteMap = hashMapOf("foo" to 1, "bar" to 2)
println(readWriteMap["foo"])  // выведет "1"
val snapshot: Map<String, Int> = HashMap(readWriteMap)