Коллекции
Коллекция является так называемым надтипом как множества, так и списка. Коллекция объединяет их общие свойства и возможности. Список, помимо возможностей коллекции, имеет возможность индексирования. Множество, помимо возможностей коллекции, не добавляет в себя уже имеющиеся элементы.
Коллекция 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. - Если
anull, результат Элвис-оператора равенb.
В примере с neighbors и в том и в другом случае мы имеем результат типа List<String>, поэтому и данная функция имеет тип результата List<String>.