Skip to content

Latest commit

 

History

History
252 lines (194 loc) · 16.8 KB

properties.md

File metadata and controls

252 lines (194 loc) · 16.8 KB
type layout category title url
doc
reference
Syntax
Свойства и поля

Свойства и поля

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

Классы в Kotlin могут иметь свойства: изменяемые (mutable) и неизменяемые (read-only) — var и val соответственно.

public class Address {
    public var name: String = ...
    public var street: String = ...
    public var city: String = ...
    public var state: String? = ...
    public var zip: String = ...
}

Для того, чтобы воспользоваться свойством, мы просто обращаемся к его имени (как в Java):

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

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

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

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

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

Примеры:

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

Синтаксис объявления констант имеет два отличия от синтаксиса объявления изменяемых переменных: во-первых, объявление начинается с ключевого слова val вместо var, а во-вторых, объявление сеттера запрещено:

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

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

val isEmpty: Boolean
    get() = this.size == 0

Пользовательский сеттер выглядит примерно так:

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

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

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

var setterVisibility: String = "abc"
    private set // сеттер имеет private доступ и стандартную реализацию

var setterWithAnnotation: Any? = null
    @Inject set // аннотирование сеттера с помощью Inject

Backing Fields

Классы в Kotlin не могут иметь полей. Т.е. переменные, которые вы объявляете внутри класса только выглядят и ведут себя как поля из Java, хотя на самом деле являются свойствами, т.к. для них неявно реализуются методы get и set. А сама переменная, в которой находится значение свойства, называется backing field. Однако, иногда, при использовании пользовательских методов доступа, необходимо иметь доступ к backing field. Для этих целей Kotlin предоставляет автоматическое backing field, к которому можно обратиться с помощью идентификатора field:

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

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

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

Например, в нижестоящем примере не будет никакого backing field:

val isEmpty: Boolean
    get() = this.size == 0

Backing Properties

Если вы хотите предпринять что-то такое, что выходит за рамки вышеуказанной схемы "неявного backing field", вы всегда можете использовать backing property:

private var _table: Map<String, Int>? = null
public val table: Map<String, Int>
    get() {
        if (_table == null) {
            _table = HashMap() // параметры типа вычисляются автоматически (ориг.: "Type parameters are inferred")
        }
        return _table ?: throw AssertionError("Set to null by another thread")
    }

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

Константы времени компиляции

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

  • Находиться на самом высоком уровне или быть членом объекта object
  • Быть проинициализированными значением типа String или значением примитивного типа
  • Не иметь переопределённого геттера

Такие свойства могут быть использованы в аннотациях:

const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated"

@Deprecated(SUBSYSTEM_DEPRECATED) fun foo() { ... }

Свойства с поздней инициализацией

Обычно, свойства, объявленные non-null типом, должны быть проинициализированы в конструкторе. Однако, довольно часто это неосуществимо. К примеру, свойства могут быть инициализированы через внедрение зависимостей, в установочном методе (ориг.: "setup method") юнит-теста или в методе onCreate в Android. В таком случае вы не можете обеспечить non-null инициализацию в конструкторе, но всё равно хотите избежать проверок на null при обращении внутри тела класса к такому свойству.

Для того, чтобы справиться с такой задачей, вы можете пометить свойство модификатором lateinit:

public class MyTest {
    lateinit var subject: TestSubject

    @SetUp fun setup() {
        subject = TestSubject()
    }

    @Test fun test() {
        subject.method()  // объект инициализирован, проверять на null не нужно
    }
}

Такой модификатор может быть использован только с var свойствами, объявленными внутри тела класса (не в главном конструкторе, и только тогда, когда свойство не имеет пользовательских геттеров и сеттеров) и, начиная с Kotlin 1.2, со свойствами, расположенными на верхнем уровне, и локальными переменными. Тип такого свойства должен быть non-null и не должен быть примитивным.

Доступ к lateinit свойству до того, как оно проинициализировано, выбрасывает специальное исключение, которое чётко обозначает, что свойство не было определено.

Проверка инициализации lateinit var (начиная с версии 1.2)

Чтобы проверить, была ли проинициализировано lateinit var свойство, используйте .isInitialized метод ссылки на это свойство:

if (foo::bar.isInitialized) {
    println(foo.bar)
}

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

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

См. Переопределение членов класса

Делегированные свойства

Самый распространённый тип свойств просто считывает (или записывает) данные из backing field. Тем не менее, с пользовательскими геттерами и сеттерами мы можем реализовать совершенно любое поведение свойства. В реальности, существуют общепринятые шаблоны того, как могут работать свойства. Несколько примеров:

  • Вычисление значения свойства при первом доступе к нему (ленивые свойства)
  • Чтение из ассоциативного списка с помощью заданного ключа
  • Доступ к базе данных
  • Оповещение listener'а в момент доступа и т.п.

Такие распространённые поведения свойств могут быть реализованы в виде библиотек с помощью делегированных свойств.