Базовый синтаксис классов
  #
Васильев Андрей Михайлович, 2024
Версии презентации
  Классы
  #
Класс — это элемент исходного кода приложения, описывающий абстрактный тип
данных и его частичную или полную реализацию
Классы в Kotlin объявляются с помощью использования ключевого слова class.
Объявление класса состоит из
- имени класса
- заголовка (указания типов его параметров, основного конструктора и т.п)
- и тела класса, заключённого в фигурные скобки
И заголовок, и тело класса являются необязательными составляющими. Если
у класса нет тела, фигурные скобки могут быть опущены
  Конструкторы
  #
Класс в Kotlin может иметь основной конструктор (primary constructor) и один или
более дополнительных конструкторов (secondary constructors)
Основной конструктор является частью заголовка класса, его объявление идёт сразу
после имени класса (и необязательных параметров).
class Person constructor(firstName: String) { /*...*/ }
Если у основного конструктора нет аннотаций и модификаторов видимости, ключевое
слово constructor может быть опущено.
class Person(firstName: String) { /*...*/ }
  Инициализация
  #
Основной конструктор не может содержать в себе исполняемого кода.
Инициализирующий код может быть помещён в соответствующие блоки (initializers
blocks), которые помечаются словом init.
При создании экземпляра класса блоки инициализации выполняются в том порядке, в
котором они идут в теле класса, чередуясь с инициализацией свойств.
class InitOrderDemo(name: String) {
    val firstProperty = "Первое свойство: $name"
    
    init {
        println("Первый блок инициализации: ${name}")
    }
    
    val secondProperty = "Второе свойство: ${name.length}"
    
    init {
        println("Второй блок инициализации: ${name.length}")
    }
}
  Иницализация свойств
  #
Обратите внимание, что параметры основного конструктора могут быть использованы в инициализирующем блоке. Они также могут быть использованы при инициализации свойств в теле класса.
class Customer(name: String) {
    val customerKey = name.uppercase()
}
  Компактная инициализация
  #
Для объявления и инициализации свойств основного конструктора в Kotlin есть
лаконичное синтаксическое решение:
class Person(val firstName: String, val lastName: String, var age: Int)
Такие объявления также могут включать в себя значения свойств класса по
умолчанию.
class Person(val firstName: String, val lastName: String, var isEmployed: Boolean = true)
Вы можете использовать завершающую запятую при объявлении свойств класса.
class Person(
    val firstName: String,
    val lastName: String,
    var age: Int, // завершающая запятая
) { /*...*/ }
Свойства, объявленные в основном конструкторе, могут быть изменяемые (var) и неизменяемые (val).
  Наследование
  #
Для всех классов в Kotlin родительским суперклассом является класс Any. Он
также является родительским классом для любого класса, в котором не указан
какой-либо другой родительский класс.
class Example // Неявно наследуется от Any
У Any есть три метода: equals(), hashCode() и toString(). Эти методы
определены для всех классов в Kotlin
  Разрешение наследования
  #
По умолчанию все классы в Kotlin имеют статус final, который блокирует
возможность наследования. Чтобы сделать класс наследуемым, его нужно пометить
ключевым словом open.
open class Base // Класс открыт для наследования
Для явного объявления суперкласса мы помещаем его имя за знаком двоеточия в
оглавлении класса:
open class Base(p: Int)
class Derived(p: Int) : Base(p)
Если у класса есть основной конструктор, базовый тип может (и должен) быть
проинициализирован там же, с использованием параметров основного конструктора.
  Переопределение методов класса
  #
Kotlin требует явно указывать модификаторы и для членов, которые могут быть
переопределены, и для самого переопределения.
open class Shape {
    open fun draw() { /*...*/ }
    fun fill() { /*...*/ }
}
class Circle() : Shape() {
    override fun draw() { /*...*/ }
}
Для Circle.draw() необходим модификатор override. В случае её отсутствия
компилятор выдаст ошибку. Если у функции типа Shape.fill() нет модификатора
open, объявление метода с такой же сигнатурой в производном классе невозможно, с
override или без. Модификатор open не действует при добавлении к членам
final класса
Член класса, помеченный override, является сам по себе open, т.е. он может
быть переопределён в производных классах. Если вы хотите запретить возможность
переопределения такого члена, используйте final.
open class Rectangle() : Shape() {
    final override fun draw() { /*...*/ }
}
  Порядок инициализации производного класса
  #
При создании нового экземпляра класса в первую очередь выполняется инициализация
базового класса (этому шагу предшествует только оценка аргументов, передаваемых
в конструктор базового класса) и, таким образом, происходит до запуска логики
инициализации производного класса.
open class Base(val name: String) {
    init { println("Инициализация класса Base") }
    open val size: Int = 
        name.length.also { println("Инициализация свойства size в класса Base: $it") }
}
class Derived(
    name: String,
    val lastName: String,
) : Base(name.replaceFirstChar { it.uppercase() }.also { println("Аргументы, переданные в конструктор класса Base: $it") }) {
    init { println("Инициализация класса Derived") }
    override val size: Int =
        (super.size + lastName.length).also { println("Инициализация свойства size в классе Derived: $it") }
}
fun main() {
    println("Построение класса Derived(\"hello\", \"world\")")
    Derived("hello", "world")
}
  Вспомогательные объекты
  #
Объявление объекта внутри класса может быть отмечено ключевым словом companion.
class MyClass {
    companion object Factory {
        fun create(): MyClass = MyClass()
    }
}
Для вызова членов такого companion объекта используется имя класса.
val instance = MyClass.create()
Необязательно указывать имя вспомогательного объекта. Тогда он будет назван Companion.
class MyClass {
    companion object { }
}
val x = MyClass.Companion
Члены класса могут получить доступ к private членам соответствующего вспомогательного объекта.