Как 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".

Комментарии (0)