Базовый синтаксис классов #

Васильев Андрей Михайлович, 2024

Версии презентации

Классы #

Класс — это элемент исходного кода приложения, описывающий абстрактный тип данных и его частичную или полную реализацию

Классы в Kotlin объявляются с помощью использования ключевого слова class.

class Person { /*...*/ }

Объявление класса состоит из

  • имени класса
  • заголовка (указания типов его параметров, основного конструктора и т.п)
  • и тела класса, заключённого в фигурные скобки

И заголовок, и тело класса являются необязательными составляющими. Если у класса нет тела, фигурные скобки могут быть опущены

class Empty

Конструкторы #

Класс в 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 членам соответствующего вспомогательного объекта.