Враховуючи наступний клас Котліна:
data class Test(val value: Int)
Як би я перевизначив Intгеттер, щоб він повертав 0, якщо значення від’ємне?
Якщо це неможливо, якими методами можна досягти відповідного результату?
Враховуючи наступний клас Котліна:
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с, але коли ви подзвонили .value, вони будуть мати той же результат. Це суперечливо, і хоча це може спрацювати для вас, інші люди у вашій команді, які бачать, що це клас даних, можуть випадково використовувати його, не усвідомлюючи, як ви його змінили / змусили його працювати не так, як очікувалося (тобто такий підхід не міг би т правильно працювати в а 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 { ... }). Також, можливо, клас даних - це не те, що вам потрібно, оскільки за допомогою класів, що не належать до даних, ви можете відокремити свої властивості від параметрів конструктора. Краще чітко вказати свої наміри щодо змінності у визначенні класу. Якщо ці поля в будь-якому випадку також можуть бути змінними, то клас даних чудовий, але майже всі мої класи даних незмінні.
Здається, це один (серед інших) прикрих недоліків Котліна.
Здається, єдиним розумним рішенням, яке повністю зберігає зворотну сумісність класу, є перетворення його в звичайний клас (не клас "даних") та реалізація вручну (за допомогою IDE) методів: hashCode ( ), дорівнює (), toString (), copy () та компонентN ()
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модифікується в блоці init equalsі hashCode не порушено.