[kotlin] Kotlin 데이터 클래스 용 getter 재정의

다음 Kotlin 클래스가 주어지면 :

data class Test(val value: Int)

Int값이 음수이면 0을 반환하도록 getter를 어떻게 재정의 합니까?

이것이 가능하지 않다면 적절한 결과를 얻기위한 몇 가지 기술은 무엇입니까?



답변

매일 Kotlin을 작성하는 데 거의 1 년을 보낸 후 이와 같은 데이터 클래스를 재정의하려는 시도가 나쁜 습관이라는 것을 알게되었습니다. 이에 대한 세 가지 유효한 접근 방식이 있으며, 제시 한 후에 다른 답변이 제안한 접근 방식이 왜 나쁜지 설명하겠습니다.

  1. data class잘못된 값으로 생성자를 호출하기 전에 값을 0 이상으로 변경하는 비즈니스 논리를 만드십시오 . 이것은 아마도 대부분의 경우에 가장 좋은 방법 일 것입니다.

  2. 를 사용하지 마십시오 data class. 일반을 사용하고 classIDE에서 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
    }
    

다른 답변이 제안하는 잘못된 접근 방식 :

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

이 접근법의 문제점은 데이터 클래스 가 실제로 이와 같은 데이터를 변경하기위한 것이 아니라는 것입니다. 그들은 실제로 데이터를 보관하기위한 것입니다. 이 같은 데이터 클래스의 게터을 재정의하는 것을 의미 Test(0)하고 Test(-1)것없는 equal서로 다른 것 hashCode들,하지만 당신이 전화했을 때 .value, 그들은 같은 결과를 가질 것이다. 이것은 일관성이 없으며 이것이 당신에게는 효과가있을 수 있지만 이것이 데이터 클래스라고 생각하는 팀의 다른 사람들은 당신이 그것을 어떻게 변경했는지 / 예상대로 작동하지 않게 만들 었는지 깨닫지 못한 채 실수로 그것을 오용 할 수 있습니다. t Map또는 a Set) 에서 올바르게 작동합니다 .


답변

다음과 같이 시도해 볼 수 있습니다.

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
  • 데이터 클래스에서 기본 생성자의 매개 변수를 val또는 로 표시해야합니다 var.

  • 나는 값 할당하고 있습니다 _value로를 value속성에 원하는 이름을 사용하기 위해.

  • 설명하신 논리로 속성에 대한 사용자 지정 접근자를 정의했습니다.


답변

대답은 실제로 사용하는 기능에 따라 다릅니다 data. @EPadron은 멋진 트릭 (개선 버전)을 언급했습니다.

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

그 의지는 예상대로 EI가있다, 작동 하나 개의 권리, 필드, 하나 게터를 equals, hashcode하고 component1. 캐치는 저것 toString이며 copy이상합니다.

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

문제를 해결하기 위해 toString손으로 재정의 할 수 있습니다. 매개 변수 이름 지정을 수정하는 방법은 없지만 전혀 사용하지 않는 방법을 알고 있습니다 data.


답변

나는 이것이 오래된 질문이라는 것을 알고 있지만 아무도 가치를 비공개로 만들고 다음과 같이 사용자 정의 getter를 작성할 가능성을 언급하지 않은 것 같습니다.

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

Kotlin은 비공개 필드에 대한 기본 getter를 생성하지 않으므로 완벽하게 유효해야합니다.

그러나 그렇지 않으면 데이터 클래스가 데이터를 보관하기위한 것이며 “비즈니스”로직을 하드 코딩하지 않아야한다는 spierce7에 확실히 동의합니다.


답변

나는 당신의 대답을 보았고, 나는 데이터 클래스가 데이터를 보유하기위한 것이라는 데 동의하지만 때로는 그들로부터 무언가를 만들어야합니다.

다음은 데이터 클래스로 수행하는 작업이며 일부 속성을 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)
    }
}


답변

이것은 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)
    }

}


답변

중단하지 않고 필요한 것을 달성하는 가장 좋은 방법은 다음 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()경우와 약간 다른 결과를 생성 하지만 일관성 있고 ._valuevalueTestData(0).toString() == TestData(-1).toString()