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

Как создать свойство в Котлине, у которого есть частный геттер (или просто его нет), но имеет публичный сеттер?

var status
private get

не работает с ошибкой: Getter visibility must be the same as property visibility

В моем случае причина в Java-взаимодействии: я хочу, чтобы мой Java-код мог вызвать setStatus, но не getStatus.

Ответ 1

В настоящее время в Котлине невозможно иметь свойство с сеттера, которое более заметно, чем свойство. В этом вопросе есть проблема с языковым дизайном в этом вопросе, не стесняйтесь смотреть/голосовать за него или делиться своими прецедентами: https://youtrack.jetbrains.com/issue/KT-3110 p >

Ответ 2

В текущей версии Kotlin (1.0.3) единственным вариантом является отдельный метод setter:

class Test {
    private var name: String = "name"

    fun setName(name: String) {
        this.name = name
    }
}

Если вы хотите ограничить доступ внешних библиотек к получателю, вы можете использовать internal модификатор видимости, позволяющий по-прежнему использовать синтаксис свойств в пределах библиотека:

class Test {
    internal var name: String = "name"
    fun setName(name: String) { this.name = name }
}

fun usage(){
    val t = Test()
    t.name = "New"
}

Ответ 3

Начиная с Kotlin 1.0, можно получить свойства только для записи с ошибками во время компиляции, используя обходной путь, основанный на @Deprecated.

Реализация

Kotlin позволяет помечать функции как устаревшие с уровнем ERROR, что приводит к ошибке времени компиляции при вызове. Аннотируя метод доступа get свойства как устарелое из-за ошибки, в сочетании с вспомогательным полем (так что частное чтение все еще возможно), достигается желаемое поведение:

class WriteOnly {
    private var backing: Int = 0

    var property: Int
        @Deprecated("Property can only be written.", level = DeprecationLevel.ERROR)
        get() = throw NotImplementedError()
        set(value) { backing = value }

    val exposed get() = backing // public API
}

Использование:

val wo = WriteOnly()
wo.property = 20         // write: OK

val i: Int = wo.property // read: compile error
val j: Int = wo.exposed  // read value through other property

Ошибка компиляции тоже весьма полезна:

Использование 'getter for property: Int' является ошибкой. Собственность может быть только написана.


Случаи применения

  1. Основным вариантом использования, очевидно, являются API, которые позволяют записывать свойства, но не читать их:

    user.password = "secret"
    val pw = user.password // forbidden
    
  2. Другой сценарий - это свойство, которое изменяет внутреннее состояние, но не сохраняется как поле. (Можно сделать более элегантно, используя другой дизайн).

    body.thrust_force = velocity
    body.gravity_force = Vector(0, 0, 9.8)
    // only total force accessible, component vectors are lost
    val f = body.forces
    
  3. Этот шаблон также полезен для DSL следующего вида:

    server {
        port = 80
        host = "www.example.com"
    }
    

    В таких случаях значения просто используются в качестве одноразовых настроек, и описанный здесь механизм только для записи может предотвратить случайное чтение свойства (которое еще может не инициализироваться).


Ограничения

Поскольку эта функция не была разработана для этого варианта использования, она имеет определенные ограничения:

  • Если доступ осуществляется с использованием ссылки на свойство, ошибка времени компиляции превращается в ошибку времени выполнения:

    val ref = wo::property
    val x = ref.get() // throws NotImplementedError
    
  • То же самое относится и к размышлению.

  • Эта функция не может быть передана в делегат, поскольку метод getValue() не может использоваться с by.

Ответ 4

Достаточно просто

var prop:Type = blah
    private set(newValue: Type { prop = newValue }

Таким образом, опора/набор остается доступной внутри класса, но не выставляется снаружи.