29 - POO - data class

Hemos dicho que una clase encapsula un conjunto de funcionalidades (métodos) y datos (propiedades)

En muchas situaciones queremos almacenar un conjunto de datos sin necesidad de implementar funcionalidades, en estos casos el lenguaje Kotlin nos provee de una estructura llamada: data class.

Problema 1

Declarar un data class llamado Articulo que almacene el código del producto, su descripción y precio. Definir luego varios objetos de dicha data class en la main.

Proyecto128 - Principal.kt

data class Articulo(var codigo: Int, var descripcion: String, var precio: Float)

fun main(parametro: Array<String>) {
    val articulo1 = Articulo(1, "papas", 34f)
    var articulo2 = Articulo(2, "manzanas", 24f)
    println(articulo1)
    println(articulo2)
    val puntero = articulo1
    puntero.precio = 100f
    println(articulo1)
    var articulo3 = articulo1.copy()
    articulo1.precio = 200f
    println(articulo1)
    println(articulo3)
    if (articulo1 == articulo3)
        println("Son iguales $articulo1 y $articulo3")
    else
        println("Son distintos $articulo1 y $articulo3")
    articulo3.precio = 200f
    if (articulo1 == articulo3)
        println("Son iguales $articulo1 y $articulo3")
    else
        println("Son distintos $articulo1 y $articulo3")
}

La sintaxis para la declaración de un data class es:

data class Articulo(var codigo: Int, var descripcion: String, var precio: Float)

Le antecedemos la palabra clave data y en el constructor definimos las propiedades que contiene dicho data class (las propiedades pueden ser de tipo var o val)

Para definir objetos de un data class es idéntico a la definición de objetos de una clase común:

fun main(parametro: Array<String>) {
    val articulo1 = Articulo(1, "papas", 34f)
    var articulo2 = Articulo(2, "manzanas", 24f)

Si le pasamos a la función println una variable de tipo data class nos muestra el nombre del data class, los nombres de las propiedades y sus valores:

    println(articulo1) // Articulo(codigo=1, descripcion=papas, precio=34.0)
    println(articulo2) // Articulo(codigo=2, descripcion=manzanas, precio=24.0)

En realidad todo data class tiene una serie de métodos básicos: toString, copy etc., luego cuando pasamos el data class a la función println lo que sucede es que dicha función llama al método toString, el mismo resultado por pantalla tenemos si escribimos:

    println(articulo1.toString()) // Articulo(codigo=1, descripcion=papas, precio=34.0)

Podemos asignar a una variable un objeto de un determinado data class:

    val puntero = articulo1

Luego la variable puntero tiene la referencia al mismo objeto referenciado por articulo1, si cambiamos la propiedad precio mediante la variable puntero:

    puntero.precio = 100f

El contenido de articulo1 ahora es:

    println(articulo1) // Articulo(codigo=1, descripcion=papas, precio=100.0)

Para obtener una copia de un objeto de tipo data class debemos llamar al método copy:

    var articulo3 = articulo1.copy()
    articulo1.precio = 200f
    println(articulo1) // Articulo(codigo=1, descripcion=papas, precio=200.0)
    println(articulo3) // Articulo(codigo=1, descripcion=papas, precio=100.0)

La variable articulo3 apunta a un objeto distinto a la variable articulo1. Esto se ve cuando modificamos la propiedad precio del articulo1 no se refleja en la propiedad precio de articulo3.

Cuando utilizamos el operador == en objetos de tipo data class se verifica verdadero si los contenidos de todas sus propiedades tienen almacenado igual valor:

    if (articulo1 == articulo3)
        println("Son iguales $articulo1 y $articulo3")
    else
        println("Son distintos $articulo1 y $articulo3")
    articulo3.precio = 200f
    if (articulo1 == articulo3)
        println("Son iguales $articulo1 y $articulo3")
    else
        println("Son distintos $articulo1 y $articulo3")

Redefinición de métodos de un data class.

Hemos dicho que al declarar un data class ya heredamos una serie de métodos que nos son útiles para procesar luego los objetos que definamos de dicho data class.

En Kotlin podemos sobreescribir cualquiera de los métodos que nos provee un data class y definir un nuevo algoritmo al mismo.

Problema 2

Declarar un data class llamado Persona que almacene el nombre y la edad. Sobreescribir el método toString para retornar un String con la concatenación del nombre y la edad separadas por una coma.

Proyecto129 - Principal.kt

data class Persona(var nombre: String, var edad: Int) {
    override fun toString(): String {
        return "$nombre, $edad"
    }
}

fun main(parametro: Array<String>) {
    var persona1 = Persona("Juan", 22)
    var persona2 = Persona("Ana", 59)
    println(persona1)
    println(persona2)
}

Declaramos el data class Persona e implementamos el método toString que como todo data class ya tiene dicho método debemos anteceder al nombre del método la palabra clave override:

data class Persona(var nombre: String, var edad: Int) {
    override fun toString(): String {
        return "$nombre, $edad"
    }
}

En la main definimos dos objetos del tipo data class Persona:

fun main(parametro: Array<String>) {
    var persona1 = Persona("Juan", 22)
    var persona2 = Persona("Ana", 59)

Cuando llamamos a la función println y le pasamos persona1 luego se ejecuta el método toString que implementamos nosotros y nos muestra:

    println(persona1) // Juan, 22
    println(persona2) // Ana, 59

Recordemos que podemos hacer que el método toString sea más concisa implementando una función con una única expresión:

    override fun toString() = "$nombre, $edad"

Propiedades declaradas en el cuerpo de la clase.

Veamos con un ejemplo como podemos definir propiedades dentro del cuerpo del data class:

data class Jugador(val nombre:String) {
    var puntos:Int=0
}

fun main() {
    val jugador1=Jugador("Pedro")
    val jugador2=Jugador("Pedro")
    jugador1.puntos=10
    jugador2.puntos=20
    if (jugador1==jugador2)
        println("Tiene en mismo nombre los jugadores")
    else
        println("No tienen el mismo nombre los jugadores")
}

Si ejecutamos el programa el if se verifica verdadero, esto debido que solo tiene en cuenta las propiedades definidas en el contructor primario de la clase (en nuestro ejemplo el "nombre")
Pero podemos tener otras propiedades en el data class como la propiedad puntos.

Sólo la propiedad "nombre" será utilizada dentro de los métodos: toString(), equals(), hashCode(), y copy().

Desestructuración de data class

Podemos extraer algunas propiedades de un data class con una sintaxis sencilla.

data class Circulo(val x: Int, val y: Int, val radio: Int)

fun main() {
    val circulo1 = Circulo(10, 3, 40)
    val (centrox, centroy) = circulo1
    println("Punto central del circulo ($centrox,$centroy)")
}

Como vemos podemos extraer el valor de las dos primeras propiedades del data class, asignando a dos variables:

    val (centrox, centroy) = punto1

Métodos componentN

El compilador de Kotlin crea un método por cada una de las propiedades definidas en el constructor, luego dichos métodos tienen el nombre 'component' seguido por un número que representa la posición en su declaración:

data class Circulo(val x: Int, val y: Int, val radio: Int)

fun main() {
    val circulo1 = Circulo(10, 3, 40)
    val centrox=circulo1.component1()
    val centroy=circulo1.component2()
    val radio=circulo1.component3()
    println("$centrox $centroy $radio")
}

Si bien puede tener poco sentido llamar a los métodos componentN, los mismos son indispensables cuando queremos recorrer un arreglo con un for y desestructurar sus propiedades (la instrucción for los requiere):

data class Circulo(val x: Int, val y: Int, val radio: Int)

fun main() {
    val circulos = arrayOf<Circulo>(
        Circulo(10, 3, 40),
        Circulo(20, 6, 50),
        Circulo(30, 9, 10),
    )
    for((x,y,radio) in circulos)
        println("Coordenada x=$x, coordenada y=$y, radio=$radio")
}

Las variables x,y y radio almacenan los valores retornados por component1(), component2() y component3().

Problema propuesto

  • Plantear un data class llamado Dado con una única propiedad llamada valor. Sobreescribir el método toString para que muestre tantos asteriscos como indica la propiedad valor.
Solución
Proyecto130

data class Dado (var valor: Int) {
    override fun toString(): String {
        var cadena = ""
        for(i in 1..valor)
            cadena = cadena +"*"
        return cadena
    }
}

fun main(parametro: Array<String>) {
    val dado1 = Dado(4)
    val dado2 = Dado(6)
    val dado3 = Dado(1)
    println(dado1)
    println(dado2)
    println(dado3)
}