Учитывая следующий класс Kotlin:
data class Test(val value: Int)
Как мне переопределить Intгеттер, чтобы он возвращал 0, если значение отрицательное?
Если это невозможно, каковы методы достижения подходящего результата?
Учитывая следующий класс Kotlin:
data class Test(val value: Int)
Как мне переопределить Intгеттер, чтобы он возвращал 0, если значение отрицательное?
Если это невозможно, каковы методы достижения подходящего результата?
Ответы:
Потратив почти год на написание 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друг друга, и у них были бы разные hashCodes, но когда вы вызываете .value, они будут иметь тот же результат. Это непоследовательно, и хотя это может сработать для вас, другие люди в вашей команде, которые видят, что это класс данных, могут случайно злоупотребить им, не понимая, как вы его изменили / заставили его работать не так, как ожидалось (т. Е. Этот подход не t работать правильно в а Mapили а Set).
data class class(@JsonProperty("iss_position") private val position: Map<String, Double>) { val latitude = position["latitude"]; val longitude = position["longitude"] }, и считаю, что это неплохо для моего случая, tbh. Что Вы думаете об этом? (были и другие поля, и поэтому я считаю, что для меня не имело смысла воссоздавать эту вложенную структуру 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
}
Это будет работает , как и ожидалось, е имеет одно поле, один поглотитель, справа 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 в том, что классы данных предназначены для хранения данных, и вам следует избегать жесткого кодирования там «бизнес-логики».
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 не нарушается.