覆盖Kotlin数据类的getter

时间:2016-07-20 22:46:07

标签: kotlin

鉴于以下Kotlin类:

data class Test(val value: Int)

我如何覆盖Int getter,如果值为负,则返回0?

如果无法做到这一点,有哪些技巧可以达到合适的效果?

8 个答案:

答案 0 :(得分:89)

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

  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()
      }
    }
    
  3. 在对象上创建一个额外的安全属性,它可以执行您想要的操作,而不是具有有效覆盖的私有值。

    data class Test(val value: Int) {
      val safeValue: Int
        get() = if (value < 0) 0 else value
    }
    
  4. 其他答案暗示的不好的方法:

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

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

答案 1 :(得分:26)

您可以尝试这样的事情:

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
  • 在数据类中,您必须使用valvar标记主要构造函数的参数。

  • 我将_value的值分配给value,以便为该属性使用所需的名称。

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

答案 2 :(得分:6)

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

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

这将按预期工作,例如,它有一个字段,一个getter,右equalshashcodecomponent1。问题是toStringcopy很奇怪:

println(Test(1))          // prints: Test(_value=1)
Test(1).copy(_value = 5)  // <- weird naming

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

答案 3 :(得分:2)

我已经看到了您的答案,我同意数据类仅用于保存数据,但是有时我们需要从中获取一些东西。

这是我在处理数据类的工作,我将一些属性从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)
    }
}

答案 4 :(得分:1)

似乎是一个古老但有趣的问题。 只想贡献一个选项:

data class Test(@JvmField val value: Int){
    fun getValue() = if(value<0) 0 else value
}

现在您可以覆盖 getValue,并且仍然可以使用 component1()。

答案 5 :(得分:0)

这似乎是Kotlin的一个(除此之外)恼人的缺点。

似乎唯一合理的解决方案是完全保持类的向后兼容性,将其转换为常规类(不是“数据”类),并手工实现(借助IDE)方法:hashCode(),equals(),toString(),copy()和componentN()

class Data3(i: Int)
{
    var i: Int = i

    override fun equals(other: Any?): Boolean
    {
        if (this === other) return true
        if (other?.javaClass != javaClass) return false

        other as Data3

        if (i != other.i) return false

        return true
    }

    override fun hashCode(): Int
    {
        return i
    }

    override fun toString(): String
    {
        return "Data3(i=$i)"
    }

    fun component1():Int = i

    fun copy(i: Int = this.i): Data3
    {
        return Data3(i)
    }

}

答案 6 :(得分:0)

我知道这是一个老问题,但似乎没有人提到将价值私有化并写下这样的定制吸气剂的可能性:

data class Test(private val value: Int) {
    fun getValue(): Int = if (value < 0) 0 else value
}

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

但是我绝对同意spierce7数据类用于保存数据,你应该避免硬编码&#34; business&#34;那里的逻辑。

答案 7 :(得分:-1)

我发现以下是在不破坏equalshashCode的情况下实现所需功能的最佳方法:

data class TestData(private var _value: Int) {
    init {
        _value = if (_value < 0) 0 else _value
    }

    val value: Int
        get() = _value
}

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

// Test copy()
assert(0 == TestData(-1).copy().value)
assert(0 == TestData(1).copy(-1).value)
assert(1 == TestData(-1).copy(1).value)

// Test toString()
assert("TestData(_value=1)" == TestData(1).toString())
assert("TestData(_value=0)" == TestData(-1).toString())
assert("TestData(_value=0)" == TestData(0).toString())
assert(TestData(0).toString() == TestData(-1).toString())

// Test equals
assert(TestData(0) == TestData(-1))
assert(TestData(0) == TestData(-1).copy())
assert(TestData(0) == TestData(1).copy(-1))
assert(TestData(1) == TestData(-1).copy(1))

// Test hashCode()
assert(TestData(0).hashCode() == TestData(-1).hashCode())
assert(TestData(1).hashCode() != TestData(-1).hashCode())

但是

首先,请注意,_valuevar,而不是val,但是另一方面,由于它是私有的并且不能继承数据类,因此很容易确保不会在课程内对其进行修改。

其次,toString()产生的结果与将_value命名为value的结果略有不同,但是与TestData(0).toString() == TestData(-1).toString()一致。