Учитывая следующий класс 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
друг друга, и у них были бы разные hashCode
s, но когда вы вызываете .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
не нарушается.