Dada la siguiente clase de Kotlin:
data class Test(val value: Int)
¿Cómo anularía el Intcaptador 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 Intcaptador 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 classmodificació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 classy haga que su IDE genere los métodos equalsy hashCodepor 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 equalentre sí y tendrían diferentes hashCodes, 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 Mapo 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 ...
Listy MutableListsin 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 valo var.
Estoy asignando el valor de _valuea valuepara 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, hashcodey component1. El problema es ese toStringy copyson 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 dataen 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 equalsy 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 _valuees 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 _valueun nombre value, pero es coherente y TestData(0).toString() == TestData(-1).toString().
_valuese está modificando en el bloque init equalsy hashCode no están rotos.