다음 Kotlin 클래스가 주어지면 :
data class Test(val value: Int)
Int
값이 음수이면 0을 반환하도록 getter를 어떻게 재정의 합니까?
이것이 가능하지 않다면 적절한 결과를 얻기위한 몇 가지 기술은 무엇입니까?
답변
매일 Kotlin을 작성하는 데 거의 1 년을 보낸 후 이와 같은 데이터 클래스를 재정의하려는 시도가 나쁜 습관이라는 것을 알게되었습니다. 이에 대한 세 가지 유효한 접근 방식이 있으며, 제시 한 후에 다른 답변이 제안한 접근 방식이 왜 나쁜지 설명하겠습니다.
-
data class
잘못된 값으로 생성자를 호출하기 전에 값을 0 이상으로 변경하는 비즈니스 논리를 만드십시오 . 이것은 아마도 대부분의 경우에 가장 좋은 방법 일 것입니다. -
를 사용하지 마십시오
data class
. 일반을 사용하고class
IDE에서equals
및hashCode
메서드를 생성하도록 합니다 (또는 필요하지 않은 경우 생성하지 않음). 예, 개체의 속성이 변경된 경우 다시 생성해야하지만 개체를 완전히 제어 할 수 있습니다.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 }
다른 답변이 제안하는 잘못된 접근 방식 :
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)
}
}
답변
중단하지 않고 필요한 것을 달성하는 가장 좋은 방법은 다음 equals
과 hashCode
같습니다.
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())
하나,
첫째, _value
이 var
아니라는 점에 유의하십시오. val
반면에 개인용이고 데이터 클래스를 상속 할 수 없기 때문에 클래스 내에서 수정되지 않도록하는 것이 상당히 쉽습니다.
둘째, 라는 이름의 toString()
경우와 약간 다른 결과를 생성 하지만 일관성 있고 ._value
value
TestData(0).toString() == TestData(-1).toString()