Глиф Учебники

Урок 9. Классы, data class и object

Курс «Kotlin» · урок 9 из 10 · 4 минуты чтения

Встроенных типов — Int, String, List — хватает для простых задач. Но программа про школу работает с учениками, программа магазина — с товарами. Класс позволяет описать свой тип: какие у него данные (свойства) и что он умеет (методы). Конкретное значение такого типа называется объектом.

Первый класс

Kotlin
class Person(val name: String, var age: Int) {
    fun info(): String {
        return "$name, $age"
    }

    fun birthday() {
        age++
    }
}

fun main() {
    val p = Person("Anna", 20)
    println(p.info())
    p.birthday()
    println(p.age)
    println(p.name)
}
Вывод
Anna, 20
21
Anna

Что здесь есть:

Объектов одного класса можно создать сколько угодно, у каждого свои значения свойств.

класс — описание типа class Person свойства val name: String var age: Int методы fun info() fun birthday() объекты — конкретные значения Person("Anna", 20) Person("Oleg", 25) p объект Person name = "Anna" age = 20 → 21 21 — после p.birthday() ещё объект Person name = "Oleg" age = 25
Класс Person описывает свойства и методы, а вызовы конструктора создают объекты со своими значениями; p.birthday() меняет age у p с 20 на 21.

Свойства в теле класса и init

Свойства можно объявлять и внутри класса. Код, который должен выполниться при создании объекта, пишут в блоке init:

Kotlin
class Rectangle(val width: Int, val height: Int) {
    val area = width * height

    init {
        println("Rectangle $width x $height created")
    }

    val isSquare: Boolean
        get() = width == height
}

fun main() {
    val r = Rectangle(3, 4)
    println(r.area)
    println(r.isSquare)
}
Вывод
Rectangle 3 x 4 created
12
false

private: скрыть лишнее

Свойство или метод с private видны только внутри класса. Так класс защищает свои данные от неправильных изменений снаружи:

Kotlin
class Account(val owner: String) {
    private var balance = 0

    fun deposit(amount: Int) {
        if (amount > 0) {
            balance += amount
        }
    }

    fun show() = "$owner: $balance"
}

fun main() {
    val acc = Account("Oleg")
    acc.deposit(100)
    acc.deposit(-50)
    println(acc.show())
}
Вывод
Oleg: 100

Строка acc.balance = 1000000 в main не соберётся: balance закрыт. Изменить его можно только через deposit, а там отрицательная сумма отбрасывается.

снаружи — в main acc.deposit(100) acc.deposit(-50) acc.balance = 1000000 class Account deposit(amount) if (amount > 0) −50 не проходит проверку +100 private balance 100 не соберётся: private
Свойство balance закрыто словом private: снаружи его меняет только метод deposit, который пропускает 100 и отбрасывает −50, а прямое присваивание не соберётся.

Как объект выглядит при выводе

Если вывести объект обычного класса, println(p), получится служебная запись вроде Person@4e50df2e — имя класса и число, разное при каждом запуске. Чтобы объект выводился понятно, классу нужен метод toString — или можно сделать класс data class.

data class

data class — класс для хранения данных. Компилятор сам добавляет в него полезные методы:

Kotlin
data class Point(val x: Int, val y: Int)

fun main() {
    val a = Point(1, 2)
    val b = Point(1, 2)
    println(a)
    println(a == b)
    val c = a.copy(y = 5)
    println(c)
    val (x, y) = c
    println("$x $y")
}
Вывод
Point(x=1, y=2)
true
Point(x=1, y=5)
1 5
class Point data class Point a и b — Point(1, 2) println(a) Point@… имя и число Point(x=1, y=2) a == b false разные объекты true по содержимому a.copy(y = 5) нет Point(x=1, y=5) val (x, y) = c нет x = 1, y = 5
Обычный класс и data class с одинаковыми свойствами: data class выводится понятно, сравнивается по содержимому, умеет copy и раскладывается по переменным.

Тело в фигурных скобках data class не обязательно — хватает одной строки. Зачем нужен data class и какие у него ограничения — в справке data class — зачем нужен.

object: единственный объект

Иногда нужен ровно один объект на всю программу — настройки, счётчик, журнал. Для этого вместо class пишут object:

Kotlin
object Counter {
    var count = 0

    fun increment() {
        count++
    }
}

fun main() {
    Counter.increment()
    Counter.increment()
    println(Counter.count)
}
Вывод
2

Создавать object не нужно и нельзя: он существует сам, обращение — по его имени. Такой шаблон называется синглтоном (singleton — одиночка).

companion object

Внутри класса можно объявить companion object — его свойства и методы вызываются через имя класса, без объекта. Так делают, например, «фабрики» — функции, которые создают объекты:

Kotlin
class User(val name: String) {
    companion object {
        fun guest() = User("guest")
    }
}

fun main() {
    val u = User.guest()
    println(u.name)
}
Вывод
guest

В Java для этого служит слово static. В Kotlin его нет: общее для всего класса кладут в companion object, а просто функции — вне классов, на верхний уровень файла.

Коротко

Проверьте себя

1. Как создать объект класса class Cat(val name: String)?
2. Что выведет программа?
Kotlin
data class Book(val title: String, val pages: Int)

fun main() {
    val b = Book("Kotlin", 300)
    println(b.copy(pages = 350))
}
3. Чем object отличается от class?
4. Что значит private перед свойством?