Свойства
Объявление свойств
Свойства в классах Kotlin могут быть объявлены либо как изменяемые (mutable) и неизменяемые (read-only) — 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() // в Kotlin нет никакого слова `new`
result.name = address.name // вызов методов доступа
result.street = address.street
// ...
return result
}
Геттеры и сеттеры
Полный синтаксис объявления свойства выглядит так:
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
Если вы определяете пользовательский сеттер, он будет вызываться каждый раз, когда вы присваиваете значение свойству, за исключением его инициализации. Пользовательский сеттер выглядит так:
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
Теневые поля
В Kotlin поле используется только как часть свойства для хранения его значения в памяти. Поля не могут быть объявлены напрямую.
Однако, когда свойству требуется теневое поле (backing field), Kotlin предоставляет его автоматически.
На это теневое поле можно обратиться в методах доступа, используя идентификатор field
:
var counter = 0 // инициализатор назначает резервное поле напрямую
set(value) {
if (value >= 0)
field = value // значение при инициализации записывается
// прямиком в backing field
// counter = value // ERROR StackOverflow: Использование 'counter' сделало бы сеттер рекурсивным
}
Идентификатор field
может быть использован только в методах доступа к свойству.
Теневое поле будет сгенерировано для свойства, если оно использует стандартную реализацию как минимум одного из методов доступа,
либо если пользовательский метод доступа ссылается на него через идентификатор field
.
Например, в примере ниже не будет никакого теневого поля:
val isEmpty: Boolean
get() = this.size == 0
Теневые свойства
Если вы хотите предпринять что-то такое, что выходит за рамки вышеуказанной схемы неявного теневого поля, вы всегда можете использовать теневое свойство (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")
}
В JVM: доступ к приватным свойствам со стандартными геттерами и сеттерами оптимизируется таким образом, что вызов функции не происходит.
Константы времени компиляции
Если значение константного (read-only) свойства известно во время компиляции, пометьте его как константы времени компиляции,
используя модификатор const
. Такие свойства должны соответствовать следующим требованиям:
- Находиться на самом высоком уровне или быть членами объявления
object
или вспомогательного объекта; - Быть проинициализированными значением типа
String
или значением примитивного типа; - Не иметь переопределённого геттера.
Такие свойства могут быть использованы в аннотациях.
const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated"
@Deprecated(SUBSYSTEM_DEPRECATED) fun foo() { ... }
Свойства и переменные с поздней инициализацией
Обычно, свойства, объявленные non-null типом, должны быть проинициализированы в конструкторе. Однако часто бывает так, что делать это неудобно. К примеру, свойства могут быть инициализированы через внедрение зависимостей или в установочном методе (ориг.: setup method) юнит-теста. В таком случае вы не можете обеспечить non-null инициализацию в конструкторе, но всё равно хотите избежать проверок на null при обращении внутри тела класса к такому свойству.
Для того чтобы справиться с такой задачей, вы можете пометить свойство модификатором lateinit
.
public class MyTest {
lateinit var subject: TestSubject
@SetUp fun setup() {
subject = TestSubject()
}
@Test fun test() {
subject.method() // объект инициализирован, проверять на null не нужно
}
}
Такой модификатор может быть использован только с var
свойствами, объявленными внутри тела класса (не в основном конструкторе,
и только тогда, когда свойство не имеет пользовательских геттеров и сеттеров), со свойствами верхнего уровня и локальными переменными.
Тип такого свойства должен быть non-null и не должен быть примитивным.
Доступ к lateinit
свойству до того, как оно проинициализировано, выбрасывает специальное исключение, которое чётко обозначает
свойство, к которому осуществляется доступ, и тот факт, что оно не было инициализировано.
Проверка инициализации lateinit var
Чтобы проверить, было ли проинициализировано lateinit var
свойство, используйте .isInitialized
метод ссылки на это свойство.
if (foo::bar.isInitialized) {
println(foo.bar)
}
Эта проверка возможна только для лексически доступных свойств, то есть объявленных в том же типе, или в одном из внешних типов, или глобальных свойств, объявленных в том же файле.
Переопределение свойств
См. Переопределение свойств класса.
Делегированные свойства
Самый простой тип свойств просто считывает (или записывает) данные из теневого поля. Тем не менее с пользовательскими геттерами и сеттерами мы можем реализовать совершенно любое поведение свойства. Где-то между простотой первого вида и разнообразием второго существуют общепринятые шаблоны того, что могут делать свойства. Несколько примеров: вычисление значения свойства при первом доступе к нему (ленивые значения), чтение из ассоциативного списка с помощью заданного ключа, доступ к базе данных, оповещение listener’а в момент доступа.
Такие распространённые поведения свойств могут быть реализованы в виде библиотек с помощью делегированных свойств.