Avatar ·

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

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

Решение: явное указание видимости сеттера

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

class User(name: String) {
    var name: String = name
        private set  // сеттер приватный, геттер публичный
    
    fun updateName(newName: String) {
        // Внутри класса можно менять name
        name = newName
    }
}

fun main() {
    val user = User("Alice")
    println(user.name)  // ✅ Читать можно (геттер публичный)
    // user.name = "Bob"  // ❌ Ошибка компиляции: сеттер приватный
    user.updateName("Bob")  // ✅ Изменение через метод класса
    println(user.name)  // Bob
}

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

Ключевое слово set после объявления свойства позволяет переопределить сеттер. Если вы пишете private set, то сеттер становится приватным, а геттер остаётся с той же видимостью, что и само свойство (по умолчанию — public). Также можно указать protected set или internal set.

Вариант с конструктором класса

Если свойство объявлено в первичном конструкторе, синтаксис немного меняется — нужно использовать private set внутри тела класса:

class User(private var name: String) {
    // В конструкторе name — приватное свойство (и геттер, и сеттер приватные)
    
    // Чтобы сделать геттер публичным, нужно объявить отдельное свойство
    var publicName: String = name
        private set
    
    fun updateName(newName: String) {
        name = newName
        publicName = newName  // синхронизация
    }
}

// Или более элегантный вариант:
class User(name: String) {
    var name: String = name
        private set
}

Почему это лучше, чем в Java

В Kotlin не нужно писать отдельные методы getName() и setName(). Всё делается через свойства с контролем доступа. Это сокращает код и делает его более читаемым. Вы также можете добавить кастомную логику в геттер или сеттер:

var age: Int = 0
    private set
    get() = field  // можно добавить валидацию или вычисления

// Пример с кастомным сеттером (приватным)
var score: Int = 0
    private set(value) {
        if (value >= 0) {
            field = value
        }
    }

Важное замечание

Если вы используете private set с var, то свойство всё равно можно изменять внутри класса (включая методы, конструкторы и инициализаторы). Снаружи — только читать. Это идеально подходит для инкапсуляции, когда внешний код не должен напрямую менять значение, но может его получать.

<<>>

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

Иногда проще и нагляднее использовать отдельное приватное свойство для хранения данных, а публичное — для чтения. Это особенно полезно, если нужно добавить валидацию или трансформацию при чтении:

class User(name: String) {
    private var _name: String = name  // приватное хранилище
    var name: String = _name          // публичное свойство только для чтения
        private set                   // сеттер приватный, но мы его не используем
    
    fun updateName(newName: String) {
        // Валидация или другая логика
        _name = newName
        // name = newName  // тоже сработает, но лучше напрямую через _name
    }
}

// Или ещё проще — сделать name только для чтения (val):
class User(name: String) {
    val name: String = name  // публичный геттер, сеттера нет вообще
    // Теперь name можно установить только в конструкторе
}

Когда использовать

Если вам нужно, чтобы свойство никогда не менялось после создания объекта — используйте val. Если же оно может меняться внутри класса, но не снаружи — var с private set — идеальный выбор. Backing-свойство (_name) оправдано, когда логика геттера или сеттера сложнее, чем простое присваивание.