Переопределить геттер для класса данных Kotlin
Учитывая следующий класс Kotlin:
data class Test(val value: Int)
Как бы я переопределить Int
геттер, чтобы он возвращал 0, если значение отрицательное?
Если это невозможно, какими методами можно достичь подходящего результата?
9 ответов
Потратив почти целый год на ежедневную работу над Kotlin, я обнаружил, что попытка переопределить такие классы данных - это плохая практика. Есть 3 правильных подхода к этому, и после того, как я представлю их, я объясню, почему подход, предложенный другими ответами, плох.
Имейте свою бизнес-логику, которая создает
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
, они будут иметь тот же результат. Это противоречиво, и хотя это может сработать для вас, другие люди в вашей команде, которые считают, что это класс данных, могут случайно использовать его, не понимая, как вы его изменили / сделали так, что он не работает должным образом (то есть этот подход не будет работать правильно в Map
или 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
чтобы использовать желаемое имя для свойства.Я определил собственный метод доступа для свойства с логикой, которую вы описали.
Я видел ваш ответ, я согласен с тем, что классы данных предназначены только для хранения данных, но иногда нам нужно что-то из них сделать.
Вот что я делаю со своим классом данных: я изменил некоторые свойства с 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)
}
}
Ответ зависит от того, какие возможности вы на самом деле используете data
обеспечивает. @EPadron упомянул хитрый трюк (улучшенная версия):
data class Test(private val _value: Int) {
val value: Int
get() = if (_value < 0) 0 else _value
}
Это будет работать, как и ожидалось, например, у него есть одно поле, один получатель, верно equals
, hashcode
а также component1
, Подвох в том, что toString
а также copy
странные
println(Test(1)) // prints: Test(_value=1)
Test(1).copy(_value = 5) // <- weird naming
Чтобы исправить проблему с toString
вы можете переопределить его руками. Я не знаю, как исправить имя параметра, но не использовать data
совсем.
Я знаю, что это старый вопрос, но, похоже, никто не упомянул о возможности сделать значение приватным и написать собственный геттер, например:
data class Test(private val value: Int) {
fun getValue(): Int = if (value < 0) 0 else value
}
Это должно быть совершенно верно, так как Kotlin не будет генерировать получатель по умолчанию для частного поля.
Но в остальном я определенно согласен с spierce7, что классы данных предназначены для хранения данных, и вам следует избегать жесткого кодирования "бизнес" логики.
Я нашел следующее, чтобы быть лучшим подходом для достижения того, что вам нужно, не ломая 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()
,
Кажется, старый, но интересный вопрос. Просто хочу внести свой вклад:
data class Test(@JvmField val value: Int){
fun getValue() = if(value<0) 0 else value
}
Теперь вы можете переопределить getValue и по-прежнему использовать component1 ().
Это, кажется, один (среди прочих) раздражающих недостатков Kotlin.
Кажется, что единственное разумное решение, которое полностью сохраняет обратную совместимость класса, - это преобразовать его в обычный класс (не класс "данных") и реализовать вручную (с помощью IDE) методы: hashCode(), равно (), 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)
}
}
Вы можете следовать шаблону Builder для этого, я думаю, что это было бы намного лучше.
Вот пример:
data class Test(
// Fields:
val email: String,
val password: String
) {
// Builder(User):
class Builder(private val email: String) {
// Fields:
private lateinit var password: String
// Methods:
fun setPassword(password: String): Builder {
// Some operation like encrypting
this.password = password
// Returning:
return this
}
fun build(): Test = Test(email, password)
}
}