Avatar ·

Kotlin: как сделать публичный геттер, но приватный сеттер для свойства?

Привет! Я только начал изучать Kotlin и перехожу с Java. В Java я привык делать поля с приватным сеттером и публичным геттером, например: ```java private String name; public String getName() { return name; } private void setName(String name) { this.name = name; } ``` Но в Kotlin я вижу, что свойства (var) автоматически создают и геттер, и сеттер. Если я пишу `var name: String`, то и геттер, и сеттер становятся публичными. А если использую `val name: String`, то свойство только для чтения — сеттера вообще нет. Мне нужно, чтобы свойство можно было читать из любого места (публичный геттер), но изменять его значение — только внутри класса (приватный сеттер). Как это правильно сделать в Kotlin? Я пробовал писать `private var name: String`, но тогда и геттер становится приватным, и я не могу прочитать свойство снаружи. Пробовал разные комбинации модификаторов доступа перед `var` и внутри `set`, но запутался. Подскажите, пожалуйста, как объявить свойство с публичным геттером и приватным сеттером? Нужен пример кода и объяснение, как это работает. Заранее спасибо! <<>>

Решение: переопределение сеттера с модификатором private

В Kotlin для создания публичного геттера и приватного сеттера нужно использовать var (изменяемое свойство) и явно переопределить сеттер, добавив перед ним модификатор private. Геттер при этом остаётся публичным по умолчанию.

class User {
    var name: String = ""
        private set // приватный сеттер, геттер остаётся публичным
}

Теперь свойство name можно читать из любого места, но изменять — только внутри класса:

fun main() {
    val user = User()
    // Чтение работает:
    println(user.name) // Выведет пустую строку

    // Изменение извне — ошибка компиляции:
    // user.name = "Alice" // Ошибка: Cannot assign to 'name': the setter is private

    // Изменение внутри класса — работает:
    user.updateName("Alice")
    println(user.name) // Выведет "Alice"
}

class User {
    var name: String = ""
        private set

    fun updateName(newName: String) {
        name = newName // Доступ к приватному сеттеру разрешён
    }
}

Как это работает

В Kotlin каждое свойство (var или val) автоматически имеет геттер и (для var) сеттер. По умолчанию оба они имеют ту же видимость, что и само свойство. Если вы пишете:

var name: String = ""

То и геттер, и сеттер — публичные. Добавив private set, вы переопределяете только сеттер, делая его приватным. Геттер остаётся публичным, так как вы его не трогали.

Важные нюансы

  • Модификатор ставится перед set — private set, protected set, internal set.
  • Если свойство объявлено как val — сеттера нет вообще, только геттер. Для изменяемого свойства с приватным сеттером используйте var.
  • Инициализация обязательна — свойство должно быть инициализировано (в теле класса или через конструктор), иначе компилятор выдаст ошибку. Исключение: если вы используете lateinit var.
  • В конструкторе — если свойство объявлено в первичном конструкторе, модификатор доступа для сеттера указывается так же:
class User(private var _name: String) {
    var name: String
        get() = _name
        private set(value) {
            _name = value
        }
}

Но это избыточно — обычно достаточно простой формы var name: String = ""; private set.

Когда это полезно

  • Инкапсуляция: вы хотите контролировать изменение свойства через методы (например, с валидацией).
  • Свойства, которые изменяются только внутри класса или в наследниках (используйте protected set).
  • Счётчики, флаги, состояния — чтобы внешний код мог читать, но не менять их напрямую.

Таким образом, решение очень простое: объявите var и добавьте private set. Никаких дополнительных классов или вспомогательных полей не нужно — Kotlin делает это элегантно.

<<>>

Альтернативный подход: использование backing field с явным геттером и сеттером

Если вам нужно больше контроля (например, валидация при установке значения или кастомная логика в геттере), можно явно объявить backing field с помощью поля field и написать полные реализации геттера и сеттера. При этом сеттер можно сделать приватным, а геттер — публичным.

class User {
    private var _name: String = ""

    var name: String
        get() = _name
        private set(value) {
            // Пример валидации
            if (value.isNotBlank()) {
                _name = value
            } else {
                println("Имя не может быть пустым")
            }
        }

    fun updateName(newName: String) {
        name = newName // Использует приватный сеттер
    }
}

Этот подход даёт те же возможности, что и private set, но позволяет:

  • Добавить проверки в сеттер (валидация, логирование).
  • Использовать разные имена для backing field и публичного свойства (хотя это необязательно).
  • Сделать геттер тоже кастомным (например, возвращать форматированное значение).

Однако, если вам не нужна дополнительная логика, проще и короче использовать стандартный синтаксис private set — это идиоматичный способ в Kotlin. Явный backing field оправдан только тогда, когда без него не обойтись.