Объектно-ориентированное программирование (ООП)

В ООП центральную роль играют классы. Классы состоят из членов, членами классов могут быть переменные, функции, конструкторы, блоки инициализации, свойства, вложенные классы, декларации объектов. В ООП класс является также типом данных.

Для использования класса необходимо сначала объявить класс и затем создать его экземпляр. Каждый экземпляр класса хранится в отдельной области оперативной памяти независимо друг от друга, так как класс определяет только структуру, а фактические значение должны быть присвоены или рассчитаны для каждого экземпляра отдельно. Необходимые значения для членов класса могут быть переданы посредством конструктора класса или присвоены членам класса путём получения доступа к его членам и присвоения новых значений.

Классы объявляются посредством ключевого слова class:

class TestClass { 
// Тело класса 
}

Классы могут иметь первостепенный конструктор, второстепенный конструктор, блоки инициализации и свойства. Первостепенный конструктор пишется сразу после названия класса в виде скобок функции с передаваемыми параметрами точно так же, как и в функциях. Второстепенный конструктор пишется внутри тела класса:

class TestClass(param : String) {
    // Переменные-члены класса
    var stringParam : String
    var intParam : Int = 0
    // Блок инициализации
    init {
        stringParam = param
    }
    // Второстепенный конструктор должен вызывать первостепенный конструктор. В данном случае первостепенный конструктор получает один параметр, а второстепенный два параметра и для правильно работы кода параметр типа String необходимо передать первостепенному конструктору вызвав его.
    constructor(param: Int, sParam : String) : this(sParam) {
        // Инициализация переменной класса под названием intParam значением аргумента param конструктора
        intParam = param
    }
}

В примере выше первостепенный конструктор принимает только один параметр типа String. В классе объявлено два переменных-члена: один с типом String, другой Int. Если переменная не инициализируется в блоке init ей необходимо присвоить значение во время объявления, как и сделано для переменной intParam. Блок init служит для сохранения полученных через конструктор значений в локальных переменных класса так как аргументы конструктора доступны для использования только в блоках инициализации и мы присвоили значение полученного аргумента param члену класса stringParam. Мы не можем присвоить его переменной intParam, так как типы должны совпадать. Блоков инициализации может быть несколько и они выполняются по порядку написания в коде. Порядок определяется порядком строк кода. Чем выше написан init блок тем раньше он выполняется.

Во второстепенном конструкторе мы объявляем, что мы можем принять два параметра: один с типом Int, другой с типом String. Из вторичных конструкторов необходимо вызывать первичный конструктор и поэтому во вторичных конструкторах также есть необходимость принимать те параметры, которые необходимы в первостепенном конструкторе или если типы получаемых данных имеют большую разницу, первостепенный конструктор необходимо объявлять пустым, а все необходимые данные получать через вторичных конструкторов. Вызов первостепенного конструктора из второстепенного осуществляется посредством написания ключевого слова this и вызова его в качестве функции и передачи необходимого параметра, полученного через второстепенный конструктор, что является вызовом первостепенного конструктора.

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

class TestClass(stringParam : String) {
    // Переменные-члены класса
    var stringParam : String
    var intParam : Int = 0
    // Блок инициализации
    init {
	// Неоднозначность исчезает, когда мы явно указываем, что переменная this.stringParam это член нашего класса, а stringParam это аргумент, полученный в конструкторе
        this.stringParam = stringParam
    }
    // Второстепенный конструктор должен вызывать первостепенный конструктор. В данном случае первостепенный конструктор получает один параметр, а второстепенный два параметра и для правильно работы кода параметр типа String необходимо передать первостепенному конструктору вызвав его.
    constructor(intParam : Int, stringParam: String ) : this(stringParam) {
        // Инициализация переменной класса под названием intParam значением аргумента param конструктора
        this.intParam = intParam
    }
}

Можно облегчить нашу работу и не писать код по инициализации, написав в первостепенном конструкторе перед названиями аргументов val или var и в таком случае компилятор получает аргументы и создаёт за нас также члены класса с типом и названием этих аргументов. Так можно делать только в первостепенном конструкторе, во второстепенных такой возможности нет:

class TestClass(var stringParam : String) {

    fun printSomething() {
        // За нас автоматически создаётся также переменная с аналогичным типом и названием как в конструкторе под названием stringParam и с типом String
        println(stringParam)
    }
}

Использование классов осуществляется посредством объявления их экземпляров и в большинстве случаев присвоение экземпляра переменной для дальнейшего взаимодействия именно с этим экземпляром. Создание экземпляра класса выглядит как вызов функции и в качестве названия функции используется название класса. Каждый экземпляр класса и является объектом в ООП.

// Создаём и инициализируем экземпляр класса TestClass и присваиваем переменной testCls
val testCls = TestClass("This is value of stringParam class member")
// Выводим значение переменной stringParam экземпляра класса TestClass, который был создан и сохранён в переменной testCls
println(testCls.stringParam)