Biorąc pod uwagę następującą klasę Kotlin:
data class Test(val value: Int)
Jak mogę zastąpić Int
metodę pobierającą, aby zwracała 0, jeśli wartość jest ujemna?
Jeśli nie jest to możliwe, jakie są techniki, aby osiągnąć odpowiedni wynik?
Biorąc pod uwagę następującą klasę Kotlin:
data class Test(val value: Int)
Jak mogę zastąpić Int
metodę pobierającą, aby zwracała 0, jeśli wartość jest ujemna?
Jeśli nie jest to możliwe, jakie są techniki, aby osiągnąć odpowiedni wynik?
Odpowiedzi:
Po spędzeniu prawie całego roku na codziennym pisaniu Kotlina stwierdziłem, że próba zastąpienia takich klas danych jest złą praktyką. Istnieją 3 ważne podejścia do tego zagadnienia, a po ich przedstawieniu wyjaśnię, dlaczego podejście sugerowane przez inne odpowiedzi jest złe.
Miej logikę biznesową, która tworzy data class
zmienną wartość na 0 lub większą przed wywołaniem konstruktora ze złą wartością. Jest to prawdopodobnie najlepsze podejście w większości przypadków.
Nie używaj data class
. Użyj zwykłego class
i niech IDE wygeneruje dla Ciebie metody equals
i hashCode
(lub nie, jeśli ich nie potrzebujesz). Tak, będziesz musiał ponownie wygenerować go, jeśli któraś z właściwości obiektu zostanie zmieniona, ale pozostaniesz z pełną kontrolą nad obiektem.
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()
}
}
Utwórz dodatkową bezpieczną właściwość na obiekcie, który robi to, co chcesz, zamiast mieć wartość prywatną, która jest skutecznie zastępowana.
data class Test(val value: Int) {
val safeValue: Int
get() = if (value < 0) 0 else value
}
Złe podejście, które sugerują inne odpowiedzi:
data class Test(private val _value: Int) {
val value: Int
get() = if (_value < 0) 0 else _value
}
Problem z tym podejściem polega na tym, że klasy danych nie są tak naprawdę przeznaczone do zmiany danych w ten sposób. Tak naprawdę służą tylko do przechowywania danych. Zastępowanie metody pobierającej dla takiej klasy danych oznaczałoby to, Test(0)
a Test(-1)
nie equal
nawzajem i miałyby różne hashCode
s, ale po wywołaniu .value
miałyby ten sam wynik. Jest to niespójne i chociaż może zadziałać dla Ciebie, inne osoby w Twoim zespole, które widzą, że jest to klasa danych, mogą przypadkowo użyć jej niewłaściwie, nie zdając sobie sprawy, jak ją zmieniłeś / sprawiłeś, że nie działa zgodnie z oczekiwaniami (tj. Takie podejście nie t działa poprawnie w a Map
lub a Set
).
data class class(@JsonProperty("iss_position") private val position: Map<String, Double>) { val latitude = position["latitude"]; val longitude = position["longitude"] }
i uważam to za całkiem dobre w moim przypadku, tbh. Co o tym myślisz? (było wiele innych pól i dlatego uważam, że nie ma sensu odtwarzać tej zagnieżdżonej struktury json w moim kodzie)
parsing a string into an int
jasno zezwalasz na logikę biznesową analizowania i obsługi
List
i MutableList
bez powodu.
Możesz spróbować czegoś takiego:
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
W klasie danych należy oznaczyć parametry konstruktora głównego za pomocą val
lub var
.
Przypisuję wartość _value
do value
, aby użyć żądanej nazwy właściwości.
Zdefiniowałem niestandardowy akcesor dla właściwości z logiką, którą opisałeś.
Odpowiedź zależy od tego, z jakich funkcji faktycznie korzystasz data
. @EPadron wspomniał o sprytnej sztuczce (ulepszonej wersji):
data class Test(private val _value: Int) {
val value: Int
get() = if (_value < 0) 0 else _value
}
Że będzie działa zgodnie z oczekiwaniami, ei ma jedno pole, jeden getter, prawda equals
, hashcode
i component1
. Haczyk jest taki toString
i copy
są dziwne:
println(Test(1)) // prints: Test(_value=1)
Test(1).copy(_value = 5) // <- weird naming
Aby rozwiązać problem toString
, możesz przedefiniować go ręcznie. Nie znam sposobu, aby naprawić nazewnictwo parametrów, ale w ogóle go nie używać data
.
Wiem, że to stare pytanie, ale wydaje się, że nikt nie wspomniał o możliwości uczynienia wartości prywatną i pisania niestandardowego gettera w ten sposób:
data class Test(private val value: Int) {
fun getValue(): Int = if (value < 0) 0 else value
}
Powinno to być całkowicie poprawne, ponieważ Kotlin nie wygeneruje domyślnego gettera dla pola prywatnego.
Ale poza tym zdecydowanie zgadzam się ze spierce7, że klasy danych służą do przechowywania danych i należy unikać zapisywania tam na sztywno logiki „biznesowej”.
val value = test.getValue()
a nie jak inne gettery val value = test.value
.getValue()
Widziałem twoją odpowiedź, zgadzam się, że klasy danych są przeznaczone tylko do przechowywania danych, ale czasami musimy coś z nich zrobić.
Oto, co robię z moją klasą danych. Zmieniłem niektóre właściwości z val na var i nadpisałem je w konstruktorze.
tak:
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 { ... }
.). Być może klasa danych nie jest tym, czego potrzebujesz, ponieważ w przypadku klas niebędących danymi możesz oddzielić swoje właściwości od parametrów konstruktora. Lepiej jest jasno określić swoje zamiary dotyczące zmienności w definicji klasy. Jeśli i tak zdarza się, że te pola są zmienne, klasa danych jest w porządku, ale prawie wszystkie moje klasy danych są niezmienne.
To wydaje się być jedną (między innymi) irytującą wadą Kotlina.
Wydaje się, że jedynym rozsądnym rozwiązaniem, które całkowicie zachowuje wsteczną kompatybilność klasy, jest przekonwertowanie jej na klasę zwykłą (nie klasę "data") i ręczne (za pomocą IDE) implementowanie metod: hashCode ( ), equals (), toString (), copy () i 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)
}
}
Znalazłem następujące czynności, aby być najlepszym podejściem do osiągnięcia tego, co trzeba bez zerwania equals
i 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())
Jednak,
Po pierwsze należy pamiętać, że _value
to var
nieval
, ale z drugiej strony, ponieważ jest to klasa prywatna i klasy danych nie mogą być dziedziczone z, dość łatwo jest upewnić się, że nie jest modyfikowana w klasie.
Po drugie, toString()
daje nieco inny wynik niż w przypadku _value
nazwy value
, ale jest spójny i TestData(0).toString() == TestData(-1).toString()
.
_value
jest modyfikowany w bloku init equals
i hashCode
nie jest uszkodzony.