Dada la siguiente clase de Kotlin:
data class Test(val value: Int)
¿Cómo anularía el Int
captador para que devuelva 0 si el valor es negativo?
Si esto no es posible, ¿cuáles son algunas técnicas para lograr un resultado adecuado?
Dada la siguiente clase de Kotlin:
data class Test(val value: Int)
¿Cómo anularía el Int
captador para que devuelva 0 si el valor es negativo?
Si esto no es posible, ¿cuáles son algunas técnicas para lograr un resultado adecuado?
Respuestas:
Después de pasar casi un año completo escribiendo Kotlin a diario, descubrí que intentar anular clases de datos como esta es una mala práctica. Hay 3 enfoques válidos para esto, y después de presentarlos, explicaré por qué el enfoque que han sugerido otras respuestas es malo.
Haga que la lógica de su negocio que crea la data class
modificación del valor sea 0 o mayor antes de llamar al constructor con el valor incorrecto. Este es probablemente el mejor enfoque para la mayoría de los casos.
No use un data class
. Use un método regular class
y haga que su IDE genere los métodos equals
y hashCode
por usted (o no lo haga, si no los necesita). Sí, tendrá que volver a generarlo si se cambia alguna de las propiedades en el objeto, pero se queda con el control total del objeto.
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()
}
}
Cree una propiedad segura adicional en el objeto que haga lo que desee en lugar de tener un valor privado que se anule efectivamente.
data class Test(val value: Int) {
val safeValue: Int
get() = if (value < 0) 0 else value
}
Un mal enfoque que sugieren otras respuestas:
data class Test(private val _value: Int) {
val value: Int
get() = if (_value < 0) 0 else _value
}
El problema con este enfoque es que las clases de datos no están diseñadas para alterar datos como este. Realmente son solo para almacenar datos. Anular el captador para una clase de datos como esta significaría que Test(0)
y Test(-1)
no lo harían equal
entre sí y tendrían diferentes hashCode
s, pero cuando llamas .value
, tendrían el mismo resultado. Esto es inconsistente y, si bien puede funcionar para usted, otras personas de su equipo que ven que esta es una clase de datos, pueden hacer un mal uso accidental sin darse cuenta de cómo lo ha alterado / hecho que no funcione como se esperaba (es decir, este enfoque no funcionaría) t funciona correctamente en a Map
o a Set
).
data class class(@JsonProperty("iss_position") private val position: Map<String, Double>) { val latitude = position["latitude"]; val longitude = position["longitude"] }
, y lo considero bastante bueno para mi caso, tbh. ¿Qué piensas sobre esto? (había ofc otros campos y, por lo tanto, creo que no tenía sentido para mí recrear esa estructura json anidada en mi código)
parsing a string into an int
, claramente está permitiendo la lógica empresarial del análisis y el manejo de errores de cadenas no numéricas en su clase de modelo ...
List
y MutableList
sin motivo.
Podrías probar algo como esto:
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
En una clase de datos, debe marcar los parámetros del constructor principal con val
o var
.
Estoy asignando el valor de _value
a value
para usar el nombre deseado para la propiedad.
Definí un acceso personalizado para la propiedad con la lógica que describiste.
La respuesta depende de las capacidades que realmente utilice data
. @EPadron mencionó un truco ingenioso (versión mejorada):
data class Test(private val _value: Int) {
val value: Int
get() = if (_value < 0) 0 else _value
}
Esa voluntad funciona como se espera, la IE tiene un campo, un captador, a la derecha equals
, hashcode
y component1
. El problema es ese toString
y copy
son raros:
println(Test(1)) // prints: Test(_value=1)
Test(1).copy(_value = 5) // <- weird naming
Para solucionar el problema toString
, puede redefinirlo con las manos. No conozco ninguna forma de arreglar el nombre de los parámetros, pero no usarlos data
en absoluto.
Sé que esta es una pregunta antigua, pero parece que nadie mencionó la posibilidad de hacer que el valor sea privado y escribir un getter personalizado de esta manera:
data class Test(private val value: Int) {
fun getValue(): Int = if (value < 0) 0 else value
}
Esto debería ser perfectamente válido ya que Kotlin no generará un getter predeterminado para el campo privado.
Pero por lo demás, definitivamente estoy de acuerdo con spierce7 en que las clases de datos son para almacenar datos y usted debe evitar codificar la lógica "comercial" allí.
val value = test.getValue()
y no como otros captadores val value = test.value
.getValue()
He visto su respuesta, estoy de acuerdo en que las clases de datos están destinadas solo a almacenar datos, pero a veces necesitamos hacer algo con ellas.
Esto es lo que estoy haciendo con mi clase de datos, cambié algunas propiedades de val a var y las superpuse en el constructor.
al igual que:
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 { ... }
). Además, tal vez una clase de datos no sea lo que desea, ya que con las clases que no son de datos puede separar sus propiedades de los parámetros de su constructor. Es mejor ser explícito con sus intenciones de mutabilidad en la definición de su clase. Si esos campos también son mutables de todos modos, entonces una clase de datos está bien, pero casi todas mis clases de datos son inmutables.
Este parece ser uno (entre otros) inconvenientes molestos de Kotlin.
Parece que la única solución razonable, que mantiene completamente la compatibilidad con versiones anteriores de la clase, es convertirla en una clase regular (no una clase de "datos") e implementar manualmente (con la ayuda del IDE) los métodos: hashCode ( ), equals (), toString (), copy () y 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)
}
}
Encontré que lo siguiente es el mejor enfoque para lograr lo que necesita sin romperse equals
y 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())
Sin embargo,
En primer lugar, cabe destacar que _value
es var
, no val
, pero por otro lado, ya que es privado y clases de datos no puede ser heredada de, que es bastante fácil para asegurarse de que no se modifica dentro de la clase.
En segundo lugar, toString()
produce un resultado ligeramente diferente al que obtendría si tuviera _value
un nombre value
, pero es coherente y TestData(0).toString() == TestData(-1).toString()
.
_value
se está modificando en el bloque init equals
y hashCode
no están rotos.