8. Основы Kotlin. Простые классы

Классы придуманы программистами в первую очередь для структурирования данных. Мы с вами уже видели, что в более-менее сложных задачах часто бывает необходимо использование составных типов — таких, как списки или строки; кстати говоря, и список и строка тоже являются классами, но только классами, определёнными в библиотеке языка.

Списки, однако, ограничены использованием элементов одного типа. Кроме этого, доступ к элементам в них происходит по номеру, что не всегда удобно. Рассмотрим, например, простую задачу вычисления расстояния между точками p1 и p2:

fun distance(x1: Double, y1: Double, x2: Double, y2: Double) = sqrt(sqr(x2 - x1) + sqr(y2 - y1))

Сразу же бросается в глаза, что в условии задачи мы говорили о двух точках, а в заголовке функции имеем вместо этого четыре параметра, по два для каждой из точек. Гораздо понятнее было бы иметь функцию в таком виде:

fun distance(p1: Point, p2: Point) = ...

Можно ли так написать заголовок функции? Да, конечно! Но для этого придётся определить в программе тип Point. В Котлине, самый простой способ это сделать выглядит так:

class Point(val x: Double, val y: Double)

fun distance(p1: Point, p2: Point) = sqrt(sqr(p2.x - p1.x) + sqr(p2.y - p1.y))

Строчка class Point(…​) определяет новый класс. Такое определение всегда начинается с ключевого слова class, за которым следует имя класса. Имена классов формируются по обычным правилам; не забывайте, что их рекомендуется начинать с прописной буквы — в отличие от имён переменных и функций.

Конструкторы и свойства класса

В круглых скобках перечисляется, какие данные необходимы для создания новой точки. Создание новой точки — это функция, но функция специальная — конструктор. Поэтому и параметры этой функции определяются почти (но не совсем) так же, как и для обычной функции. В данном случае мы видим, что создания точки необходимо задать её вещественные координаты x и y.

Внимательный читатель заметит отличие в определении x и y от обычных параметров функции — оно заключается в добавлении ключевого слова val перед именем каждого параметра. Ключевое слово val перед параметром конструктора превращает его из простого параметра в свойство класса. Свойства в данном случае задают внутреннюю структуру каждой точки. Свойство, заданное через val, можно читать, используя имя переменной (или параметра) типа Point и символ точки: p1.x. Вместо val можно использовать var (мутирующее свойство), тогда свойство можно будет также изменять: p1.x = …​. Обращение к свойству напоминает вызов функции, имеющей получателя, но в нём отсутствуют круглые скобки и набор аргументов. В этом месте можно вспомнить и понять, что list.size является свойством списка (размер), а str.length — свойством строки (длина).

Итак, каждая наша точка имеет два свойства — x и y. Обратите внимание, что, определив класс, мы определили и тип, но не определили ни одной переменной данного типа. Создаются новые точки следующим простым образом:

fun usePoints() {
    // Здесь мы просто перечисляем аргументы
    val a = Point(0.0, 3.0)
    // А здесь перед каждым аргументом мы написали имя параметра, которому он соответствует
    val b = Point(x = 4.0, y = 0.0)
    println(distance(a, b)) // 5.0
}

Каждый вызов конструктора Point создаёт новую точку, или объект (синоним — экземпляр) класса Point. Вызов distance(a, b) рассчитывает расстояние между двумя созданными точками.

Функции класса

Определение класса Point, приведённое в начале этого раздела, было пустым. Это означает, что класс содержит только два свойства x и y. Немного изменим определение класса, перенеся функцию distance внутрь него. Программисты называют такие функции членами класса.

class Point(val x: Double, val y: Double) {
    fun distance(other: Point): Double = sqrt(sqr(x - other.x) + sqr(y - other.y))
}

Такая функция вместо двух параметров имеет лишь один — точку other (другая). Однако определённая внутри класса функция имеет также получателя (мы уже сталкивались с такими функциями ранее, но ни разу не определяли их сами). Получатель функции — объект того класса, членом которого является данная функция. Например:

fun usePoints() {
    val a = Point(0.0, 3.0)
    val b = Point(4.0, 0.0)
    println(a.distance(b)) // 5.0
}

Здесь точка a стоит перед именем функции и используется функцией как получатель. Аргумент b используется как параметр other. Как результат, функция distance всё равно имеет доступ к двум точкам. Она работает с получателем, непосредственно используя свойства своего класса-владельца: x и y. Также она работает с параметром, используя его свойства как other.x и other.y.

Добавить комментарий