다음 Kotlin 클래스가 주어지면 :
data class Test(val value: Int)
Int
값이 음수이면 0을 반환하도록 getter를 어떻게 재정의 합니까?
이것이 가능하지 않다면 적절한 결과를 얻기위한 몇 가지 기술은 무엇입니까?
다음 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 class(@JsonProperty("iss_position") private val position: Map<String, Double>) { val latitude = position["latitude"]; val longitude = position["longitude"] }
, 방금 썼고 , 제 경우에 꽤 좋다고 생각합니다, tbh. 이것에 대해 어떻게 생각하십니까? (ofc 다른 필드가 있었기 때문에 내 코드에서 중첩 된 json 구조를 다시 만드는 것이 의미가 없다고 생각합니다.)
parsing a string into an int
, 당신은 명확하게 분석하고 오류가 모델 클래스에 숫자가 아닌 문자열을 처리하는 비즈니스 로직을 허용하고 ...
List
그리고 MutableList
아무 이유없이.
다음과 같이 시도해 볼 수 있습니다.
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 value = test.getValue()
다른 게터들 처럼 부르지 말고 이렇게 불러야 할 것입니다 val value = test.value
.getValue()
나는 당신의 대답을 보았고, 나는 데이터 클래스가 데이터를 보유하기위한 것이라는 데 동의하지만 때로는 그들로부터 무언가를 만들어야합니다.
다음은 데이터 클래스로 수행하는 작업이며 일부 속성을 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)
}
}
fun Recording(...): Recording { ... }
. 또한 비 데이터 클래스를 사용하면 생성자 매개 변수에서 속성을 분리 할 수 있기 때문에 데이터 클래스가 원하는 것이 아닐 수도 있습니다. 클래스 정의에서 변경 의도를 명시 적으로 지정하는 것이 좋습니다. 이러한 필드도 어쨌든 변경 가능하다면 데이터 클래스는 괜찮지 만 거의 모든 데이터 클래스는 변경할 수 없습니다.
이것은 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()
_value
초기화 블록으로 변형되고 있고 equals
및 hashCode
파손되지 않는다.