Враховуючи наступний клас Котліна:
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
не порушено.