Override getter for Kotlin data class
After spending almost a full year of writing Kotlin daily I've found that attempting to override data classes like this is a bad practice. There are 3 valid approaches to this, and after I present them, I'll explain why the approach other answers have suggested is bad.
Have your business logic that creates the
data class
alter the value to be 0 or greater before calling the constructor with the bad value. This is probably the best approach for most cases.-
Don't use a
data class
. Use a regularclass
and have your IDE generate theequals
andhashCode
methods for you (or don't, if you don't need them). Yes, you'll have to re-generate it if any of the properties are changed on the object, but you are left with total control of the object.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() } }
-
Create an additional safe property on the object that does what you want instead of having a private value that's effectively overriden.
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
}
The problem with this approach is that data classes aren't really meant for altering data like this. They are really just for holding data. Overriding the getter for a data class like this would mean that Test(0)
and Test(-1)
wouldn't equal
one another and would have different hashCode
s, but when you called .value
, they would have the same result. This is inconsistent, and while it may work for you, other people on your team who see this is a data class, may accidentally misuse it without realizing how you've altered it / made it not work as expected (i.e. this approach wouldn't work correctly in a Map
or a Set
).
You could try something like this:
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
In a data class you must to mark the primary constructor's parameters with either
val
orvar
.I'm assigning the value of
_value
tovalue
in order to use the desired name for the property.I defined a custom accessor for the property with the logic you described.
The answer depends on what capabilities you actually use that data
provides. @EPadron mentioned a nifty trick (improved version):
data class Test(private val _value: Int) {
val value: Int
get() = if (_value < 0) 0 else _value
}
That will works as expected, e.i it has one field, one getter, right equals
, hashcode
and component1
. The catch is that toString
and copy
are weird:
println(Test(1)) // prints: Test(_value=1)
Test(1).copy(_value = 5) // <- weird naming
To fix the problem with toString
you may redefine it by hands. I know of no way to fix the parameter naming but not to use data
at all.