覆盖Kotlin数据类的getter

spi*_*ce7 79 kotlin

鉴于以下Kotlin类:

data class Test(val value: Int)
Run Code Online (Sandbox Code Playgroud)

Int如果值为负,我将如何覆盖getter以使其返回0?

如果这是不可能的,那么有哪些技术可以获得合适的结果呢?

spi*_*ce7 117

在花了差不多整整一年写Kotlin之后,我发现尝试覆盖这样的数据类是一种不好的做法.有3种有效的方法,在我提出之后,我将解释为什么其他答案提出的方法很糟糕.

  1. data class在调用具有错误值的构造函数之前,让您的业务逻辑创建将值更改为0或更大.对于大多数情况,这可能是最好的方法.

  2. 不要使用data class.使用常规class并让您的IDE 为您生成equalshashCode方法(如果您不需要它们,则不需要).是的,如果在对象上更改了任何属性,则必须重新生成它,但是您可以完全控制对象.

    class Test(value: Int) {
      val value: Int = value
        get() = if (field < 0) 0 else field
    
      override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (other !is Test) return false
        return true
      }
    
      override fun hashCode(): Int {
        return javaClass.hashCode()
      }
    }
    
    Run Code Online (Sandbox Code Playgroud)
  3. 在对象上创建一个额外的安全属性,它可以执行您想要的操作,而不是具有有效覆盖的私有值.

    data class Test(val value: Int) {
      val safeValue: Int
        get() = if (value < 0) 0 else value
    }
    
    Run Code Online (Sandbox Code Playgroud)

其他答案暗示的坏方法:

data class Test(private val _value: Int) {
  val value: Int
    get() = if (_value < 0) 0 else _value
}
Run Code Online (Sandbox Code Playgroud)

这种方法的问题是数据类并不真正意味着改变这样的数据.它们实际上只是用于保存数据.覆盖像这样的数据类的getter意味着Test(0)并且彼此Test(-1)不会equal并且会有不同的hashCodes,但是当你调用时.value,它们将具有相同的结果.这是不一致的,虽然它可能对你有用,但是你团队中看到这个数据类的其他人可能会意外地误用它而没有意识到你是如何改变它/使它不能按预期工作(即这种方法不会'在a Map或a Set)中正确工作.


EPa*_*onU 28

你可以尝试这样的事情:

data class Test(private val _value: Int) {
  val value = _value
    get(): Int {
      return if (field < 0) 0 else field
    }
}

assert(1 == Test(1).value)
assert(0 == Test(0).value)
assert(0 == Test(-1).value)

assert(1 == Test(1)._value) // Fail because _value is private
assert(0 == Test(0)._value) // Fail because _value is private
assert(0 == Test(-1)._value) // Fail because _value is private
Run Code Online (Sandbox Code Playgroud)
  • 在数据类中,您必须使用val或标记主构造函数的参数var.

  • 我指定的值_valuevalue以使用所需的名称属性.

  • 我使用您描述的逻辑为属性定义了一个自定义访问器.

  • 这是一个糟糕的解决方案,会引入一些问题.我在答案中提供了一些更好的选择,并解释了为什么这是一个糟糕的方法. (3认同)
  • 我在 IDE 上遇到错误,它说“此处不允许使用初始化程序,因为此属性没有支持字段” (2认同)

bio*_*007 7

我知道这是一个老问题,但似乎没有人提到将值设为私有并编写自定义 getter 的可能性,如下所示:

data class Test(private val value: Int) {
    fun getValue(): Int = if (value < 0) 0 else value
}
Run Code Online (Sandbox Code Playgroud)

这应该是完全有效的,因为 Kotlin 不会为私有字段生成默认的 getter。

但除此之外,我绝对同意 spierce7 的观点,即数据类用于保存数据,您应该避免在那里硬编码“业务”逻辑。


vod*_*dan 6

答案取决于您实际使用的功能data.@EPadron提到了一个漂亮的技巧(改进版):

data class Test(private val _value: Int) {
    val value: Int
        get() = if (_value < 0) 0 else _value
}
Run Code Online (Sandbox Code Playgroud)

这将正常工作,EI它有一个领域,一个消气吧equals,hashcodecomponent1.捕获的是,toString并且copy很奇怪:

println(Test(1))          // prints: Test(_value=1)
Test(1).copy(_value = 5)  // <- weird naming
Run Code Online (Sandbox Code Playgroud)

要解决此问题,toString可以手动重新定义.我知道无法修复参数命名但根本不能使用data.


Sim*_*mou 5

我已经看到您的回答,我同意数据类仅用于保存数据,但有时我们需要利用它们来做一些事情。

这是我对我的数据类所做的,我将一些属性从 val 更改为 var,并在构造函数中覆盖它们。

像这样:

data class Recording(
    val id: Int = 0,
    val createdAt: Date = Date(),
    val path: String,
    val deleted: Boolean = false,
    var fileName: String = "",
    val duration: Int = 0,
    var format: String = " "
) {
    init {
        if (fileName.isEmpty())
            fileName = path.substring(path.lastIndexOf('\\'))

        if (format.isEmpty())
            format = path.substring(path.lastIndexOf('.'))

    }


    fun asEntity(): rc {
        return rc(id, createdAt, path, deleted, fileName, duration, format)
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 仅仅为了在初始化期间修改字段而使字段可变是一种不好的做法。最好将构造函数设为私有,然后创建一个充当构造函数的函数(即 `fun Recording(...): Recording { ... }`)。另外,数据类可能不是您想要的,因为使用非数据类,您可以将属性与构造函数参数分开。最好在类定义中明确说明您的可变性意图。如果这些字段也恰好是可变的,那么数据类就可以了,但几乎所有的数据类都是不可变的。 (2认同)