首页 文章

覆盖Kotlin数据类的getter

提问于
浏览
49

鉴于以下Kotlin类:

data class Test(val value: Int)

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

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

6 回答

  • 0

    你可以尝试这样的事情:

    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 ,以便为属性使用所需的名称 .

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

  • 26

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

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

    这将按预期工作,e.i它有 one 字段,一个getter,右 equalshashcodecomponent1 . 问题是 toStringcopy 很奇怪:

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

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

  • 6

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

    • 在调用具有错误值的构造函数之前,让创建 data class 的业务逻辑将值更改为0或更大 . This is probably the best approach for most cases.

    • 不要使用 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()
      }
    }
    
    • 在对象上创建一个额外的安全属性,它可以执行您想要的操作,而不是具有有效覆盖的私有值 .
    data class Test(val value: Int) {
      val safeValue: Int
        get() = if (value < 0) 0 else value
    }
    

    A bad approach that other answers are suggesting:

    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 ,但是当你调用 .value 时,它们会有相同的结果 . 这是不一致的,虽然它可能对你有用,但是你团队中看到这个数据类的其他人可能会意外地误用它而没有意识到你在 MapSet 中是如何正常工作的 .

  • 64

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

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

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

    但是否则我绝对同意数据类用于保存数据的spierce7,你应该避免在那里硬编码“业务”逻辑 .

  • 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)
        }
    
    }
    
  • -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() .

相关问题