Классы, объекты, свойства и функции-члены в Kotlin

Приступая к знакомству с объектно-ориентированным программированием, следует оговорить, что в Kotlin мы уже пользовались всеми его преимуществами и особенностями. Мы просто не создавали собственные классы, не определяли в них свойства и методы, которыми будут обладать объекты, созданные на базе этих классов.

Вместо этого мы использовали те классы, которые встроены в Kotlin, его стандартную библиотеку, в основном это были базовые классы. Например, класс целых чисел Int или класс массивов Array. И хотя мы называли их типами данных, в Kotlin это то же самое что класс, класс данных.

От классов создаются конкретные объекты – экземпляры классов. Так число 10 – это экземпляр класса Int. От одного и того же класса можно создать множество разных объектов. Однако у всех объектов, созданных от одного класса, будет одинаковый перечень свойств и действий, которые с ними можно совершать.

Так у всех экземпляров, созданных от класса Array, будет свойство длинны массива. Однако значение этого свойства у каждого будет свое. Все массивы будут поддерживать операцию извлечения элемента по индексу, но из-за того, что в одном массиве 5 элементов, а в другом 50, диапазон допустимых индексов будет разным.

Идея объектно-ориентированного программирования предполагает, что в программе есть сущности-объекты и порождаемые ими явления, подобно тому, как обстоит дело в реальном мире, и так, как мы привыкли воспринимать окружающее. А выполнение программой определенной задачи во многом сводится к изменению свойств объектов с помощью функций-методов, то есть действий, которые они и над ними можно совершать.

Класс – это ничто иное как обобщенное описание свойств и действий, которыми обладают объекты данного класса.

В Kotlin классы принято определять в отдельных файлах, хотя это не обязательно. Можно в том же, где находится функция main() – точка входа в программу; также в одном файле может содержаться несколько классов.

Создадим свой класс, похожий на класс целых чисел. Назовем его NumInc. Объекты этого класса будут обладать двумя свойствами – хранить в себе число и шаг, число может изменяться на шаг. Также в классе определим две функции – увеличивающую число на шаг и уменьшающую.

class NumInc {
    var number = 0
    var step = 1
 
    fun inc() {
        number += step
    }
    fun dec() {
        number -= step
    }
}
fun main() {
    val a = NumInc()
    val b = NumInc()
 
    a.number = 10
    a.step = 2
    a.inc()
    b.dec()
 
    println("a: ${a.number}")
    println("b: ${b.number}")
}

Результат выполнение программы:

a: 12
b: -1

Создание класса и объектов в Kotlin

Мы создали класс NumInc, объекты которого обладают свойствами number и step, начальные значения которых 0 и 1. Также у объектов этого класса есть функции-члены inc() и dec().

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

В синтаксисе своего вызова функция-член отличается от обычной функции тем, что вызывается в нотации через точку: объект.функция(). Например, мы можем вызвать функцию inc() только через объект a или b, то есть a.inc() или b.inc().

Однако есть исключения. В Kotlin, как и в других объектно-ориентированных языках, многие операторы (сложение, взятие по индексу) и ключевые слова – это на самом деле функции-члены, которые вызываются с помощью таких специальных знаков и слов. Для каждой такой функции, определяемой в классе, есть свое особое имя, регламентируемое языком программирования. Определение подобной функции в классе называется переопределением оператора.

В main() мы создали два объекта от класса NumInc. Это подобно тому, как если бы создавались объекты от класса Array.

val c = Array(3, {0})

Если вы посмотрите тип переменных a и b (Ctrl + Shift + P), увидите, что их тип NumInc. В полном варианте определение переменной выглядело бы так:

val a: NumInc = NumInc()

При создании объектов после имени класса ставятся круглые скобки. Только в нашем случае в скобках мы ничего не передаем.

При вызове класса мы могли бы что-нибудь передать в скобках для инициации свойств объекта. Однако вместо этого жестко "вшили" эти значения в сам класс. Поэтому все объекты класса NumInc исходно обладают одинаковыми значениями свойств: number = 0 и step = 1.

Несмотря на то, что и у объекта a и у b значения этих свойств в момент создания одинаковое, у каждого объекта свой number и свой step. Изменяя значение переменной a.number, мы никаким образом не меняем значение b.number.

Методы inc() и dec() у объектов одни и те же. С каждым объектом они делают то же самое, однако при работе используют свойства того объекта, на который они были вызваны (того, что стоит перед точкой). Если inc() вызывается на a, то есть a.inc(), то выражение тела этого метода будет невидимо для нас преобразовано из number += step в a.number += a.step.

Вопросы:

  1. Почему в примере значение b.number оказалось равным -1?

  2. Добавьте в класс еще одну функцию, которая увеличивает число не на шаг-свойство, а на число, которое передается в функцию в качестве аргумента.

PDF-версия курса с ответами к практическим работам

Приложение для Android "Kotlin. Курс"

Комментарии

class NumInc {
    var number = 0
    var step = 1
 
    fun inc() {
        number += step
    }
    fun dec() {
        number -= step
    }
    fun user(arg: Int) {
        number += arg
    }
}
 
fun main() {
    val a = NumInc()
    val b = NumInc()
    a.number = 5
    a.inc()
    b.step = 5
    b.dec()
    println("a=${a.number} b=${b.number}")
    a.user(10)
    println("a=${a.number}")
}