Kotlin属性的私有getter和公共setter

72
在 Kotlin 中如何创建一个具有私有 getter(或没有 getter)但具有公共 setter 的属性?
var status
private get

无法正常工作并出现错误:Getter visibility must be the same as property visibility

在我的情况中,原因是为了Java互操作性:我希望我的Java代码能够调用setStatus但不能调用getStatus


2
只是好奇:你为什么想要那样做?一个只写字段? - Fildor
私有 get 表示在类中直接使用属性,对吗? - Louis F.
是的。一个只写字段。它只能从类内部读取。 - Randy Sugianto 'Yuku'
这是一个好问题。不过,我对于写入但不读取字段的用例很好奇?我想不出来为什么你需要它不能被读取。你能分享一下你的用例吗? - aaaidan
2
@aaaidan 这是建造者模式和命令模式的典型使用情况。 - Ilya Gazman
3个回答

53
目前在Kotlin中,不可能拥有一个具有比属性更可见的setter的属性。在问题跟踪器上存在一个语言设计问题,请随时观看/投票或分享您的用例: https://youtrack.jetbrains.com/issue/KT-3110

在 Kotlin 中说,拥有一个比属性 _ 更可见的 setter 的属性与说 getter 不能比 setter 更可见是相同的吗? - lmiguelvargasf

30

在当前的 Kotlin 版本(1.0.3)中,唯一的选项是拥有单独的 setter 方法,如下所示:

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

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

如果您希望限制外部库访问getter,可以使用internal可见性修饰符,在库中仍然可以使用属性语法:

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

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

2
如果您计划让Java使用该代码,那么内部关键字最终会在Java世界中变为public。 - bj4947

9
自 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

编译错误也很有帮助,例如:

使用“获取属性:Int”的方法是错误的。属性只能被写入。


应用场景

  1. The main use case are obviously APIs that allow properties to be written, but not read:

    user.password = "secret"
    val pw = user.password // forbidden
    
  2. Another scenario is a property which modifies the internal state, but is not stored itself as a field. (Could be done more elegantly using different design).

    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. This pattern is also useful for DSLs of the following kind:

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

    In such cases, values are simply used as one-time settings, and the write-only mechanism described here can prevent accidentally reading a property (which might not be initialized yet).

限制

由于此功能并非为此用例设计,因此存在一定的限制:

  • If accessed using a property reference, the compile-time error turns into a runtime error:

    val ref = wo::property
    val x = ref.get() // throws NotImplementedError
    
  • The same is true for reflection.

  • This functionality cannot be outsourced into a delegate, because an error-deprecated getValue() method cannot be used with by.


1
这很有趣和创造性,但感觉像是一种hack。知道你能做到这一点很有趣,但我建议不要在生产代码中引入这个技巧。特别是因为它在清晰度和简洁性方面都无法击败“天真”的自定义setter替代方案。 - Konrad Morawski
我同意在声明时保持清晰/简洁,因为这不是一种常见的方法。然而,在调用时,相反的情况可能更为真实——getX/setX 方法更加冗长,并且可能会引发“为什么没有属性?”这样的问题,这在 Kotlin 中是惯用语。一个很好但相对较小的副作用是:如果 Kotlin 引入了只写属性,那么调用站点语法已经是正确的 ;) - TheOperator

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接