Свойства
Объявление свойств
Свойства в классах 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–2022 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/properties.html