Как Kotlin неявно создает за вас поля, геттеры и сеттеры, когда вы объявляете свойство?
Терминология свойств и полей в Kotlin может немного сбивать с толку, потому что технически в Kotlin нет полей. Вы не можете объявить поле. Все — свойства!
Однако, во избежании путаницы, я предпочитаю разделять определения полей и свойств на следующей основе:
Полями являются приватные переменные-члены класса. Это то, для чего выделена память.
Свойствами являются публичные или защищенные (protected) функциями геттеры и сеттеры, которые позволяют вам получить доступ к приватным полям.
Я считаю хорошей идеей разграничивать эти понятия таким образом, потому что это способствует моему пониманию, а также упрощает объяснение связанных с этим вещей.
Неявное поле, неявный геттер/сеттер
Давайте рассмотрим следующий пример. name
является свойством.
class Person {
var name = "Vincent"
}
Когда вы объявляете подобное свойство, Kotlin неявно создает для вас поле, и функции геттер и сеттер.
В декомпилированном коде Java это выглядит так:
public final class Person {
@NotNull
private String name = "Vincent";
@NotNull
public final String getName() {
return this.name;
}
public final void setName(@NotNull String var1) {
Intrinsics.checkNotNullParameter(var1, "<set-?>");
this.name = var1;
}
}
Как вы можете заметить, private String name
— поле (переменная-член). getName()
и setName()
являются геттером и сеттером свойства (также известные как свойства-аксессоры)
Неявное поле, явный геттер/сеттер
Конечно, вы также можете явно определить геттер и сеттер свойства, что также будет генерировать очень похожий декомпилированный код Java.
class Person {
var name: String = "Vincent"
get() { return field }
set(value) { field = value }
}
field
здесь создается неявно. Это то, что также называют backing полями (Backing Fields). Предоставленные свойства-аксессоры (т.е. get()
и set()
) называются backing свойствами (Backing Properties).
Явное поле, явный геттер/сеттер
Вы также можете явно определить поле. В общем, тут все и так понятно.
class Person {
private var _name:String = "Vincent"
var name: String
get() { return _name }
set(value) { _name = value }
}
Вместо неявного field
, здесь у явное поле _name
.
Private set или backing свойства?
Допустим, теперь вы хотите, чтобы свойство name
было доступно только для чтения вне класса. Вы можете ограничить сеттер свойства с помощью private set
.
Например:
class Person {
var name: String = "Vincent"
private set
}
Или вы можете использовать backing
свойство. Удалите set()
и поменяйте var
на val
.
class Person {
private var _name:String = "Vincent"
val name: String
get() { return _name }
}
Оба примера генерируют один и тот же декомпилированный Java-код, как показано ниже. Обратите внимание, что функция setName()
отсюда удалена.
public final class Person {
@NotNull
private String name = "Vincent";
@NotNull
public final String getName() {
return this.name;
}
}
Лично я предпочитаю вариант с private set
, попросту потому что там меньше кода.
Неправильное использование private set
Но подождите, не так быстро. Что, если вы преобразуете следующее резервное свойство
class MainViewModel: ViewModel() {
private val _state: MutableState<Int?> = mutableStateOf(null)
val state: State<Int?> = _state
/*...*/
}
с применением private set
class MainViewModel: ViewModel() {
var state: MutableState<Int?> = mutableStateOf(null)
private set
}
Это очень хороший пример неправильного использования private set
. Что этот код на самом деле означает так это то, что вы не можете назначить новую переменную для state
вне класса MainViewModel
. Но сама переменная state
по-прежнему является изменяемой (это означает, что вы можете изменить ее значение).
Приведенное выше backing
свойство предоставляет State
, доступное только для чтения. Если изменить изменить его на private set
, то мы нарушим это поведение. Таким образом, в этом сценарии вам точно не следует использовать private set.
Это касается любых изменяемых данных.
Заключение
Я считаю, что понимать и различать концепции полей и свойств очень важно.
Когда вы объявляете свойство, это не выделяет новую память, потому что это просто функция геттер или сеттер. Однако, если подразумевается неявная реализация поля (как в приведенных выше примерах кода), то тогда выделение памяти все-таки происходит.
Наконец, не стоит преобразовывать каждое backing свойство в private set
. Вы не должны этого делать в особенности для изменяемых данных.
Все разработчики знают, что код очень часто превращается со временем в "большой комок грязи" (Big Ball of Mud), поддерживать который очень тяжело и дорого. На открытом уроке 13 июня мы обсудим, как поддерживать чистую архитектуру приложения и контролируемо внедрять изменения. Также мы исследуем библиотеку для реализации бизнес-процессов, написанную на Kotlin. В завершение мы посмотрим пример модуля бизнес-логики, в котором сконцентрированы все требования заказчика.
Записаться на открытый урок можно на странице курса "Kotlin Backend Developer. Professional".