Свойства
Объявление свойств
Свойства в классах Kotlin могут быть объявлены как изменяемые, используя ключевое слово var, или как неизменяемые (только для чтения), используя ключевое слово 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() // there's no 'new' keyword in Kotlin
result.name = address.name // accessors are called
result.street = address.street
// ...
return result
}
Геттеры и сеттеры
Полный синтаксис объявления свойства следующий:
var <propertyName>[: <PropertyType>] [= <property_initializer>]
[<getter>]
[<setter>]
Инициализатор, геттер и сеттер являются необязательными. Тип свойства является необязательным, если его можно определить из инициализатора или типа возвращаемого значения геттера, как показано ниже:
var initialized = 1 // has type Int, default getter and setter // var allByDefault // ERROR: explicit initializer required, default getter and setter implied
Полный синтаксис объявления неизменяемого (только для чтения) свойства отличается от изменяемого свойства двумя способами: он начинается с val вместо var и не допускает сеттер:
val simple: Int? // has type Int, default getter, must be initialized in constructor val inferredType = 1 // has type Int and a default getter
Можно определить пользовательские аксессоры для свойства. Если вы определите пользовательский геттер, он будет вызываться каждый раз при обращении к свойству (так можно реализовать вычисляемое свойство). Вот пример пользовательского геттера:
//sampleStart
class Rectangle(val width: Int, val height: Int) {
val area: Int // property type is optional since it can be inferred from the getter's return type
get() = this.width * this.height
}
//sampleEnd
fun main() {
val rectangle = Rectangle(3, 4)
println("Width=${rectangle.width}, height=${rectangle.height}, area=${rectangle.area}")
}
Можно опустить тип свойства, если его можно определить из геттера:
val area get() = this.width * this.height
Если вы определите пользовательский сеттер, он будет вызываться каждый раз при присваивании значения свойству, за исключением его инициализации. Пользовательский сеттер выглядит так:
var stringRepresentation: String
get() = this.toString()
set(value) {
setDataFromString(value) // parses the string and assigns values to other properties
}
По соглашению, имя параметра сеттера — value, но вы можете выбрать другое имя, если хотите.
Если вам нужно добавить аннотацию к аксессору или изменить его видимость, но вам не нужно изменять реализацию по умолчанию, вы можете определить аксессор без определения его тела:
var setterVisibility: String = "abc"
private set // the setter is private and has the default implementation
var setterWithAnnotation: Any? = null
@Inject set // annotate the setter with Inject
Базовые поля
В Kotlin поле используется только как часть свойства для хранения его значения в памяти. Поля нельзя объявлять напрямую. Однако, когда свойству требуется базовое поле, Kotlin автоматически его предоставляет. К этому базовому полю можно обратиться в аксессорах, используя идентификатор field.
var counter = 0 // the initializer assigns the backing field directly
set(value) {
if (value >= 0)
field = value
// counter = value // ERROR StackOverflow: Using actual name 'counter' would make setter recursive
}
Идентификатор field может использоваться только в аксессорах свойства.
Базовое поле будет сгенерировано для свойства, если оно использует реализацию по умолчанию хотя бы одного из аксессоров или если пользовательский аксессор ссылается на него через идентификатор field.
Например, базового поля не будет в следующем случае:
val isEmpty: Boolean
get() = this.size == 0
Базовые свойства
Если вы хотите сделать что-то, что не подходит под схему неявного базового поля, вы всегда можете использовать базовое свойство:
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")
}
Константы времени компиляции
Если значение неизменяемого свойства известно на этапе компиляции, отметьте его как константу времени компиляции, используя модификатор const. Такое свойство должно соответствовать следующим требованиям:
Это должно быть свойство верхнего уровня или член объявления
objectили компаньон-объекта.Это должно быть инициализировано значением типа
Stringили примитивного типа.Это не может быть пользовательским геттером.
Компилятор встроит использование константы, заменив ссылку на константу её фактическим значением. Однако поле не будет удалено и, следовательно, к нему можно будет обратиться с помощью рефлексии.
Такие свойства также могут использоваться в аннотациях:
const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated"
@Deprecated(SUBSYSTEM_DEPRECATED) fun foo() { ... }
Свойства и переменные с отложенной инициализацией
Обычно свойства, объявленные как имеющие не-null тип, должны быть инициализированы в конструкторе. Однако часто это неудобно. Например, свойства могут быть инициализированы с помощью инъекции зависимостей или в методе настройки юнит-теста. В этих случаях вы не можете предоставить инициализатор не-null в конструкторе, но все равно хотите избежать проверок на null при обращении к свойству внутри тела класса.
Для таких случаев вы можете отметить свойство модификатором lateinit.
public class MyTest {
lateinit var subject: TestSubject
@SetUp fun setup() {
subject = TestSubject()
}
@Test fun test() {
subject.method() // dereference directly
}
}
Этот модификатор может использоваться для var свойств, объявленных внутри тела класса (не в первичном конструкторе и только в том случае, если у свойства нет пользовательского геттера или сеттера), а также для свойств верхнего уровня и локальных переменных. Тип свойства или переменной должен быть не-null, и это не должен быть примитивный тип.
Обращение к свойству с lateinit перед его инициализацией вызывает специальное исключение, которое четко идентифицирует обращаемое свойство и тот факт, что оно не было инициализировано.
Проверка инициализации lateinit var
Чтобы проверить, было ли уже инициализировано lateinit var, используйте .isInitialized для ссылки на это свойство:
if (foo::bar.isInitialized) {
println(foo.bar)
}
Эта проверка доступна только для свойств, которые лексически доступны при объявлении в том же типе, во внешних типах или на верхнем уровне в том же файле.
Переопределение свойств
Делегированные свойства
Наиболее распространенный тип свойства просто считывает (и, возможно, записывает) в базовое поле, но пользовательские геттеры и сеттеры позволяют использовать свойства для реализации любого поведения свойства. Где-то между простотой первого типа и разнообразием второго типа есть общие шаблоны для того, что могут делать свойства. Некоторые примеры: ленивые значения, чтение из карты по заданному ключу, доступ к базе данных, уведомление слушателя при обращении.
Такое общее поведение может быть реализовано как библиотеки с помощью делегированных свойств.
© 2010–2023 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/properties.html