Коллекции
Коллекция является так называемым надтипом как множества, так и списка. Коллекция объединяет их общие свойства и возможности. Список, помимо возможностей коллекции, имеет возможность индексирования. Множество, помимо возможностей коллекции, не добавляет в себя уже имеющиеся элементы.
Коллекция Collection<T>
хранит в себе однотипные элементы типа T
. Возможностями коллекции являются:
- Определение количества элементов (размера), пустоты, непустоты.
- Определение вхождения элемента и перебор элементов (in).
- Сложение с другой коллекцией или с отдельным элементом.
- Для мутирующей коллекции
MutableCollection<T>
, также — добавление и удаление элемента.
Коллекции используются в ситуации, когда программисту безразличен конкретный вид коллекции. В частности, многие операции, уже привычные нам для списков, на самом деле определены для коллекций.
Ассоциативные массивы (карты)
Ассоциативный массив (он же — карта или словарь) Map<K, V>
подобен обычному массиву или, вернее, списку. Разница заключается в том, что индексом в списке является целое число от 0 до list.size - 1
, а индексом в ассоциативном массиве может быть всё что угодно. Тип используемого индекса (ключа) определяется параметром карты K
, а тип хранимых значений — параметром V
. Для массива vertices
, ключом является строка String
(имя вершины), а значением — сама вершина Vertex
.
Класс Graph
использует только две операции над vertices
— создание и индексацию. Для создания карты используются метод mapOf(…)
для обычной карты Map<K, V>
, либо mutableMapOf(…)
для мутирующей карты. Пример вызова:
val map = mapOf("John" to 87, "Mike" to 41, "Fred" to 24)
Такой вызов создаст карту типа Map<String, Int>
, ключом которой является строка, а значением — целое число. По ключу “John” карта хранит число 87 и так далее. Функция key to value
создаёт пару из ключа и значения, а из перечисления пар создаётся сама карта.
Индексация для карты происходит как и для массива, но индекс должен совпадать по типу с ключом карты. В частности, индексом vertices
является строка (имя интересующей нас вершины). В обычной карте можно только читать значения по ключу, а в мутирующую карту их можно добавлять. Следует отметить, что ключи в карте не могут повторяться — при попытке добавить в карту новое значение для уже существующего ключа произойдёт удаление старого значения по этому ключу.
Любопытно поведение карты при обращении по несуществующему ключу, например, map["Tom"]
для карты из примера. В отличие от списка, никаких исключений при этом не формируется; однако, результат данной операции — null. Это так называемая нулевая ссылка, которая уже встречалась нам в шестом уроке. Тип результата операции обращения по индексу в данном случае Int?
, что означает Int
либо null.
Безопасные операции
Рассмотрим теперь подробнее функцию neighbors()
из класса Graph
:
fun neighbors(name: String) = vertices[name]?.neighbors?.map { it.name } ?: listOf()
Разберём определение этой функции. Как уже говорилось выше, результат vertices[name]
может оказаться нулевой ссылкой, тип этого выражения Vertex?
. У типа Vertex
(без ?) имеется свойство neighbors
; однако, обратиться к нему просто как vertices[name].neighbors
нельзя, так как null не имеет ни свойств, ни методов. Поэтому приходиться использовать так называемое безопасное обращение: vertices[name]?.neighbors
. Результатом такого обращения будет значение свойства neighbors
у полученной из карты вершины; если же ключа name
в карте нет, то вместо вершины мы будем иметь null и результатом безопасного обращения также будет null. Тип выражения vertices[name]?.neighbors
— MutableSet<Vertex>?
.
В дальнейшем мы тем же способом вызываем функцию высшего порядка map
, замещая каждую вершину из полученного множества её именем и получая список имён. Поскольку обращение к map
тоже происходит через ?.
, то при отсутствии найденной вершины с самого начала вместо списка имён мы получим null. Тип выражения vertices[name]?.neighbors?.map { it.name }
— List<String>?
.
Наконец, в конце выражения используется так называемый Элвис-оператор. Элвис-оператор имеет два аргумента, например, a ?: b
и действует так:
- Если
a
не null, результат Элвис-оператора равенa
. - Если
a
null, результат Элвис-оператора равенb
.
В примере с neighbors
и в том и в другом случае мы имеем результат типа List<String>
, поэтому и данная функция имеет тип результата List<String>
.