Свойства классов #

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

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

Объявление свойств #

Свойства в классах Kotlin объявляются либо как изменяемые (mutable) — var, либо как неизменяемые (read-only) — val.

class Address {
    var name: String = "Holmes, Sherlock"
    var street: String = "Baker"
    var city: String = "London"
    var state: String? = null
    var zip: String = "123456"
}

Для того чтобы воспользоваться свойством, просто обратитесь к нему по имени.

fun copyAddress(address: Address): Address {
    val result = Address() // в Kotlin нет никакого слова `new`
    result.name = address.name // вызов методов доступа
    result.street = address.street
    // ...
    return result
}

Работа ключевого слова val #

Объявление поля с помощью ключевого слова val

class Sample(val name: String)

можно сравнить со следующим кодом на Java

public class Sample {
    private String name;
    public Sample(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
}

Работа ключевого слова var #

Объявление поля с помощью ключевого слова var

class Counter(var value: Int)

можно сравнить со следующим кодом на Java

public class Counter {
    private int value;
    public Counter(int value) {
        this.value = value;
    }
    public int getValue() {
        return value;
    }
    public void setValue(int value) {
        this.value = value;
    }
}

Только изменяемые (var) свойства можно использовать в левой части присваивания. Присваивание значению val-свойства запрещено.

Геттеры и сеттеры #

Полный синтаксис объявления свойства выглядит так:

var <propertyName>[: <PropertyType>] [= <property_initializer>]
    [<getter>]
    [<setter>]

Инициализатор property_initializer, геттер и сеттер можно не указывать. Также необязательно указывать тип свойства, если он может быть выведен из инициализатора или из возвращаемого типа геттера.

var initialized = 1 // имеет тип Int, стандартный геттер и сеттер
var allByDefault    // ошибка: необходима явная инициализация, 
                    // предусмотрены стандартные геттер и сеттер

Неизменяемые свойства #

Синтаксис объявления неизменяемых свойств имеет два отличия от синтаксиса объявления изменяемых свойств:

  • объявление неизменяемого свойства начинается с ключевого слова val вместо var
  • объявление сеттера запрещено
val simple: Int? // имеет тип Int?, стандартный геттер, 
                 // должен быть инициализирован в конструкторе
val inferredType = 1 // имеет тип Int и стандартный геттер

Определение геттера #

Вы можете самостоятельно определить методы доступа для свойства

Если вы определяете пользовательский геттер, он будет вызываться каждый раз, когда вы обращаетесь к свойству (таким образом, вы можете реализовать вычисляемое свойство)

class Rectangle(val width: Int, val height: Int) {
    val area: Int
        get() = this.width * this.height // тип свойства необязателен, поскольку он может быть выведен из возвращаемого типа геттера
}

Вы можете опустить тип свойства, если его можно определить с помощью геттера.

val area get() = this.width * this.height

Переопределение свойств #

Свойство базового класса, объявленное как open, переопределяется в производном классе с модификатором override:

open class Shape {
    open val vertexCount: Int = 0
}

class Rectangle : Shape() {
    override val vertexCount: Int = 4
}

fun main() {
    println(Rectangle().vertexCount) // 4
}

Переопределить свойство можно двумя способами: инициализатором — тогда значение вычисляется один раз при создании объекта, либо пользовательским геттером — тогда значение вычисляется при каждом обращении.

Определение сеттера #

Если вы определяете пользовательский сеттер, он будет вызываться каждый раз, когда вы присваиваете значение свойству, за исключением его инициализации.

var stringRepresentation: String
    get() = this.toString()
    set(value) {
        setDataFromString(value) // парсит строку и устанавливает 
                                 // значения для других свойств
    }

По договорённости, имя параметра сеттера - value, но вы можете использовать любое другое.

Теневые поля #

В Kotlin поле используется только как часть свойства для хранения его значения в памяти. Поля не могут быть объявлены напрямую. Однако, когда свойству требуется теневое поле (backing field), Kotlin предоставляет его автоматически. На это теневое поле можно обратиться в методах доступа, используя идентификатор field:

var counter = 0 // инициализатор назначает резервное поле напрямую
    set(value) {
        if (value >= 0)
            field = value // значение при инициализации записывается 
                          // прямиком в backing field
    }

Идентификатор field может быть использован только в методах доступа к свойству.

Теневое поле будет сгенерировано для свойства, если оно использует стандартную реализацию как минимум одного из методов доступа, либо если пользовательский метод доступа ссылается на него через идентификатор field

Видимость членов класса #

По умолчанию члены класса публичны. Модификатор private ограничивает доступ телом класса, protected добавляет к нему классы-наследники, internal — весь модуль. Видимость сеттера можно ограничить отдельно от свойства:

class Counter(start: Int) {
    private val initial = start // свойство не видно снаружи класса
    var value: Int = start
        private set             // чтение доступно извне, изменение — только в классе

    fun increase() {
        value += 1
    }
}

fun main() {
    val counter = Counter(0)
    println(counter.value) // 0
    counter.increase()
    println(counter.value) // 1
}

Попытка обращения к скрытым членам извне класса не компилируется:

counter.value = 5
// error: cannot assign to 'value': the setter is private in 'Counter'
println(counter.initial)
// error: cannot access 'initial': it is private in 'Counter'

Вычисляемое свойство тоже можно закрыть модификатором private, если оно нужно только внутри класса.