Data la seguente classe Kotlin:
data class Test(val value: Int)
Come sovrascriverei il Intgetter in modo che restituisca 0 se il valore è negativo?
Se ciò non è possibile, quali sono alcune tecniche per ottenere un risultato adeguato?
Data la seguente classe Kotlin:
data class Test(val value: Int)
Come sovrascriverei il Intgetter in modo che restituisca 0 se il valore è negativo?
Se ciò non è possibile, quali sono alcune tecniche per ottenere un risultato adeguato?
Risposte:
Dopo aver trascorso quasi un anno intero a scrivere quotidianamente Kotlin, ho scoperto che tentare di sovrascrivere classi di dati come questa è una cattiva pratica. Ci sono 3 approcci validi a questo, e dopo averli presentati, spiegherò perché l'approccio suggerito da altre risposte è sbagliato.
Avere la logica aziendale che crea l' data classalterazione del valore in modo che sia 0 o maggiore prima di chiamare il costruttore con il valore errato. Questo è probabilmente l'approccio migliore per la maggior parte dei casi.
Non utilizzare un file data class. Usa un normale classe chiedi al tuo IDE di generare i metodi equalse hashCodeper te (o no, se non ne hai bisogno). Sì, dovrai rigenerarlo se una qualsiasi delle proprietà viene modificata sull'oggetto, ma ti rimane il controllo totale dell'oggetto.
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()
}
}
Crea una proprietà sicura aggiuntiva sull'oggetto che fa ciò che desideri invece di avere un valore privato che viene effettivamente ignorato.
data class Test(val value: Int) {
val safeValue: Int
get() = if (value < 0) 0 else value
}
Un cattivo approccio che altre risposte suggeriscono:
data class Test(private val _value: Int) {
val value: Int
get() = if (_value < 0) 0 else _value
}
Il problema con questo approccio è che le classi di dati non sono realmente pensate per alterare dati come questo. Sono davvero solo per contenere i dati. Ignorare il getter per una classe di dati come questa significherebbe che Test(0)e Test(-1)non sarebbero l' equalun l'altro e avrebbero hashCodes diversi , ma quando si chiama .value, avrebbero lo stesso risultato. Questo è incoerente e, sebbene possa funzionare per te, altre persone del tuo team che vedono che questa è una classe di dati, potrebbero accidentalmente usarla in modo improprio senza rendersi conto di come l'hai modificata / fatta non funzionare come previsto (cioè questo approccio non lo farebbe t funzionare correttamente in Mapao a Set).
data class class(@JsonProperty("iss_position") private val position: Map<String, Double>) { val latitude = position["latitude"]; val longitude = position["longitude"] }, e lo considero abbastanza buono per il mio caso, tbh. Cosa ne pensi di questo? (c'erano ofc altri campi e quindi credo che non avesse senso per me ricreare quella struttura json nidificata nel mio codice)
parsing a string into an int, stai chiaramente consentendo la logica aziendale dell'analisi e della gestione degli errori di stringhe non numeriche nella tua classe modello ...
Liste MutableListsenza motivo.
Potresti provare qualcosa di simile:
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
In una classe di dati è necessario contrassegnare i parametri del costruttore principale con valo var.
Assegno il valore di _valuea valueper utilizzare il nome desiderato per la proprietà.
Ho definito una funzione di accesso personalizzata per la proprietà con la logica che hai descritto.
La risposta dipende dalle funzionalità effettivamente utilizzate che datafornisce. @EPadron ha menzionato un trucco ingegnoso (versione migliorata):
data class Test(private val _value: Int) {
val value: Int
get() = if (_value < 0) 0 else _value
}
Quella volontà funziona come previsto, ei ha un campo, un getter, a destra equals, hashcodee component1. Il problema è quello toStringe copysono strani:
println(Test(1)) // prints: Test(_value=1)
Test(1).copy(_value = 5) // <- weird naming
Per risolvere il problema con toStringte puoi ridefinirlo a mano. Non conosco alcun modo per correggere la denominazione dei parametri ma non per utilizzarla dataaffatto.
So che questa è una vecchia domanda ma sembra che nessuno abbia menzionato la possibilità di rendere il valore privato e scrivere getter personalizzati in questo modo:
data class Test(private val value: Int) {
fun getValue(): Int = if (value < 0) 0 else value
}
Questo dovrebbe essere perfettamente valido poiché Kotlin non genererà un getter predefinito per il campo privato.
Ma per il resto sono assolutamente d'accordo con spierce7 sul fatto che le classi di dati servono a contenere i dati e dovresti evitare di codificare la logica "aziendale" lì.
val value = test.getValue() e non come gli altri getter val value = test.value
.getValue()
Ho visto la tua risposta, sono d'accordo sul fatto che le classi di dati siano pensate per contenere solo dati, ma a volte abbiamo bisogno di ricavarne qualcosa.
Ecco cosa sto facendo con la mia classe di dati, ho cambiato alcune proprietà da val a var e le ho sovrascritte nel costruttore.
così:
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 { ... }). Inoltre, forse una classe di dati non è ciò che desideri, poiché con classi non di dati puoi separare le tue proprietà dai parametri del costruttore. È meglio essere espliciti con le tue intenzioni di mutabilità nella definizione della tua classe. Se anche questi campi sono modificabili comunque, allora una classe di dati va bene, ma quasi tutte le mie classi di dati sono immutabili.
Questo sembra essere uno (tra gli altri) fastidiosi inconvenienti di Kotlin.
Sembra che l'unica soluzione ragionevole, che mantiene completamente la compatibilità con le versioni precedenti della classe, sia quella di convertirla in una classe normale (non una classe "dati"), e implementare a mano (con l'aiuto dell'IDE) i metodi: hashCode ( ), equals (), toString (), copy () e 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)
}
}
Ho trovato il seguente approccio migliore per ottenere ciò di cui hai bisogno senza interruzioni equalse 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())
Però,
Innanzitutto, nota che lo _valueè var, no val, ma d'altra parte, poiché è privato e le classi di dati non possono essere ereditate, è abbastanza facile assicurarsi che non venga modificato all'interno della classe.
In secondo luogo, toString()produce un risultato leggermente diverso da quello che avrebbe se _valuefosse stato nominato value, ma è coerente e TestData(0).toString() == TestData(-1).toString().
_valueviene modificato nel blocco di inizializzazione equalse hashCode non sono interrotti.