Свойства
Объявление свойств
Свойства в классах 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