Члены класса
Класс может иметь произвольное количество членов (members), которые делятся на две категории: свойства и функции. Свойства класса определяются как val (неизменяемые) или var (изменяемые). Чаще всего они используются для описания внутренней структуры класса; например, в классе Graph
свойство vertices
используется для сохранения информации о вершинах графа, а в классе Vertex
свойство neighbors
сохраняет информацию о вершинах, соседних (то есть соединённых ребром) с данной.
Функции класса определяются как fun и используются для различных операций с объектом данного класса, в данном случае — с графом. В данном случае, функции connect
служат для соединения двух вершин графа ребром — то есть, для добавления в граф нового ребра. Функция addVertex
добавляет в граф новую вершину.
Видимость
Члены класса могут иметь различную видимость. Чаще всего в Котлине используются два уровня видимости: открытый (public, по умолчанию) и закрытый (private). Для изменения уровня видимости следует указать ключевое слово public или private перед определением члена класса.
Открытые члены класса могут использоваться всеми. В любой части программы мы имеем право написать graph.connect(…)
для добавления в граф нового ребра. Закрытые члены класса могут использоваться только самим классом; при попытке написать graph.vertices
снаружи класса для обращения к свойству vertices
произойдёт ошибка при компиляции программы.
Закрытые члены класса были придуманы программистами, чтобы разграничить ответственность за разные участки программы. Действует следующий принцип: каждый класс сам отвечает за своё содержимое. В идеале, никакие операции с открытыми членами класса не должны приводить к ошибкам, и состояние объекта класса должно меняться в соответствии с выполненными операциями. Например:
fun useGraph() { val g = Graph() g.addVertex("A") g.addVertex("B") g.addVertex("C") g.addVertex("D") g.connect("A", "C") g.connect("B", "D") g.connect("B", "C") println(g.neighbors("B")) } // Должен получиться граф // A ----- C // | // | // D ----- B // println выведет: ["C", "B"]
Программист, написавший функцию useGraph
, резонно ожидает, что после выполнения приведённого кода в графе g
будет четыре вершины и три ребра, выглядяющих примерно так, как изображено в комментарии. Он также ожидает, что при поиске вершин, соседних с «B», мы получим список из вершин «C» и «D». При этом ассоциативный массив vertices
(который фактически и хранит информацию о вершинах графа), является закрытым и его не может изменять никто, кроме других членов данного класса.
Использованный здесь принцип программисты называют инкапсуляцией. Граф в данном случае подобен капсуле, на которой есть кнопки «addVertex» и «connect» (их нажатие изменяет граф), а также индикатор neighbors
(вызов соответствующей функции не изменяет граф). Всё остальное находится внутри капсулы и не видно снаружи; закрытое содержимое графа является его личным (приватным) делом.
Вложенные классы
Довольно часто бывает так, что некоторый класс не имеет смысла без какого-то другого класса. Так произошло и в нашем примере — вершина не имеет никакого смысла без графа. В этом случае класс Vertex
, соответствующий вершине, определяется внутри класса Graph
. Поскольку в данном случае класс закрытый, то и использоваться он может только внутри класса Graph
. Если бы класс Vertex
был открыт, его можно было бы использовать снаружи графа как Graph.Vertex
.
В данном случае вершина имеет свойство «имя» (name), которое задаётся в её конструкторе, и свойство «соседи» (neighbors), которое хранит мутирующее множество (MutableSet) из других вершин. При создании вершины множество её соседей пусто, но вызовы функции connect
из графа расширяют его.
Множества и списки
Множества во многих отношениях похожи на списки, но всё же отличаются от них. Множества, как и списки, содержат внутри себя некоторое количество однотипных элементов; отличие от списков состоит в том, что множество не может содержать одинаковых элементов. При попытке добавить в множество элемент, который там уже есть, множество не изменяется.
Для множества имеется возможность проверить наличие в нём определённого элемента, или же перебрать все элементы множества с помощью цикла for — обе эти возможности есть и у списков. Для множества имеется свойство size
и функции isEmpty()
, isNotEmpty()
для определения его размера. Множества можно складывать друг с другом — все перечисленные операции у списков тоже имеются. Множества в Котлине бывают обычными Set<T>
либо мутирующими MutableSet<T>
.
Является ли множество просто списком, в котором нет одинаковых элементов? Нет, это не так. Множество не поддерживает доступ по индексу, то есть в нём отсутствует операция set[i]
— как для чтения, так и для записи. Зато множество умеет значительно быстрее списка определять наличие в нём элементов element in set
. Для реализации этой операции над списком необходимо перебрать его весь, а множества имеют более сложную структуру, позволяющую находить элементы в нём быстрее.
Создаются множества в Котлине с помощью функций setOf(…)
и mutableSetOf(…)
.