
Hola habrozhiteli! Tenemos un libro publicado para estudiar Kotlin utilizando la técnica Head First, que va más allá de la sintaxis y las instrucciones para resolver problemas específicos. Este libro le dará todo lo que necesita, desde los conceptos básicos del lenguaje hasta los métodos avanzados. Y puedes practicar programación orientada a objetos y funcional.
Un extracto "Clases de datos" se presenta debajo del corte.
Trabajar con datos
Nadie quiere perder el tiempo y rehacer lo que ya se ha hecho. La mayoría de las aplicaciones usan clases para almacenar datos. Para simplificar el trabajo, los creadores de Kotlin propusieron el concepto de una clase de datos. En este capítulo, aprenderá cómo las clases de datos lo ayudan a escribir código más elegante y conciso con el que solo podía soñar antes. Veremos las funciones auxiliares de las clases de datos y aprenderemos cómo descomponer un objeto de datos en componentes. Al mismo tiempo, describiremos cómo los valores de los parámetros predeterminados hacen que el código sea más flexible, y también le presentaremos a Any, el antepasado de todas las superclases.
El operador == llama a una función llamada igual
Como ya sabe, el operador == puede usarse para verificar la igualdad. Cada vez que se ejecuta la instrucción ==, se llama a una función llamada igual. Cada objeto contiene una función igual, y la implementación de esta función determina el comportamiento del operador ==.
Por defecto, la función igual para verificar la igualdad verifica si dos referencias variables al mismo objeto.
Para entender cómo funciona, imagine dos variables de Wolf llamadas w1 y w2. Si w1 y w2 contienen referencias a un objeto Wolf, al compararlas con el operador ==, el resultado es verdadero:
Pero si w1 y w2 contienen referencias a diferentes objetos Wolf, al compararlos con el operador == se obtiene el resultado falso, incluso si los objetos contienen los mismos valores de propiedad.
Como se mencionó anteriormente, la función igual se incluye automáticamente en cada objeto que cree. ¿Pero de dónde viene esta función?
igual hereda de la superclase Cualquiera
Cada objeto contiene una función llamada igual porque su clase hereda una función de una clase llamada Cualquiera. La clase Any es el antepasado de todas las clases: la superclase resultante de todo. Cada clase que defina es una subclase de Any, y no necesita señalar esto en el programa. Por lo tanto, si escribe un código de clase llamado myClass, que se ve así:
class MyClass { ... }
El compilador lo convertirá automáticamente al siguiente formulario:
Cada clase es una subclase de Any y hereda su comportamiento. Cada clase es una subclase de Any, y no tiene que informar esto en el programa.
La importancia de cualquier herencia
Incluir Any como la superclase resultante tiene dos ventajas importantes:
- Asegura que cada clase herede el comportamiento común. La clase Any define un comportamiento importante del que depende el funcionamiento del sistema. Y dado que cada clase es una subclase de Any, todos los objetos que crea heredan este comportamiento. Entonces, la clase Any define una función llamada igual, y por lo tanto, esta función es heredada automáticamente por todos los objetos.
- Significa que el polimorfismo se puede usar con cualquier objeto. Cada clase es una subclase de Any, por lo que cualquier objeto que cree tiene la clase Any como su supertipo final. Esto significa que puede crear una función con cualquier parámetro o un tipo de retorno Any que funcione con objetos de cualquier tipo. Esto también significa que puede crear matrices polimórficas para almacenar objetos de cualquier tipo con código de la siguiente forma:
val myArray = arrayOf(Car(), Guitar(), Giraffe())
El compilador nota que cada objeto en la matriz tiene un prototipo común de Any, y por lo tanto crea una matriz de tipo Array.
El comportamiento general heredado por la clase Any merece una mirada más cercana.
Comportamiento común heredado de Any
La clase Any define varias funciones heredadas por cada clase. Aquí hay ejemplos de funciones básicas y su comportamiento:
- igual (cualquiera: Cualquiera): booleano
Comprueba si dos objetos se consideran "iguales". Por defecto, la función devuelve verdadero si se usa para verificar un objeto, o falso, para diferentes objetos. Detrás de escena, la función igual se llama cada vez que se usa el operador == en el programa.
val w1 = Wolf() val w1 = Wolf() val w2 = Wolf() val w2 = w1 println(w1.equals(w2)) println(w1.equals(w2)) false (equals false, true (equals true, w1 w2 w1 w2 .) — , w1 == w2.
- hashCode (): Int
Devuelve un código hash para un objeto. Algunas estructuras de datos suelen utilizar códigos hash para almacenar y recuperar valores de manera eficiente.
val w = Wolf() println(w.hashCode())
523429237 (Valor del código hash w)
- toString (): Cadena
Devuelve un mensaje de cadena que representa el objeto. Por defecto, el mensaje contiene el nombre de la clase y un número, que generalmente no nos importa.
val w = Wolf() println(w.toString())
Lobo @ 1f32e575Por defecto, la función igual verifica si dos objetos son el mismo objeto real.
La función igual determina el comportamiento del operador ==.
La clase Any proporciona una implementación predeterminada para todas las funciones enumeradas, y estas implementaciones son heredadas por todas las clases. Sin embargo, puede anular estas implementaciones para cambiar el comportamiento predeterminado de todas las funciones enumeradas.
Verificación de equivalencia simple de dos objetos
En algunas situaciones, debe cambiar la implementación de la función equals para cambiar el comportamiento del operador ==.
Supongamos que tiene una clase de Receta que le permite crear objetos para almacenar recetas. En tal situación, es probable que considere dos objetos de receta iguales (o equivalentes) si contienen una descripción de la misma receta. Digamos que la clase Receta se define con dos propiedades: título e isVegetarian:
class Recipe(val title: String, val isVegetarian: Boolean) { }
El operador == devolverá verdadero si se usa para comparar dos objetos Receta con las mismas propiedades, título y es vegetariano:
val r1 = Recipe("Chicken Bhuna", false) val r2 = Recipe("Chicken Bhuna", false)
Aunque puede cambiar el comportamiento del operador == escribiendo código adicional para anular la función de igualdad, los desarrolladores de Kotlin han proporcionado una solución más conveniente: crearon el concepto de una clase de datos. Veamos qué son estas clases y cómo se crean.
La clase de datos le permite crear objetos de datos.
Una clase de datos es una clase para crear objetos para almacenar datos. Incluye herramientas útiles para trabajar con datos; por ejemplo, una nueva implementación de la función equals, que verifica si dos objetos de datos contienen los mismos valores de propiedad. Si dos objetos contienen los mismos datos, pueden considerarse iguales.
Para definir una clase de datos, preceda la definición de datos habitual con la palabra clave de datos. El siguiente código convierte la clase Receta creada anteriormente en una clase de datos:
data class Recipe(val title: String, val isVegetarian: Boolean) { }
El prefijo de datos convierte una clase regular en una clase de datos.
Cómo crear objetos basados en una clase de datos
Los objetos de clase de datos se crean de la misma manera que los objetos de clase normales: llamando al constructor de esta clase. Por ejemplo, el siguiente código crea un nuevo objeto de datos de receta y lo asigna a una nueva variable llamada r1:
val r1 = Recipe("Chicken Bhuna", false)
Las clases de datos anulan automáticamente sus funciones de igualdad para cambiar el comportamiento del operador == de modo que se verifique la igualdad de los objetos en función de los valores de propiedad de cada objeto. Si, por ejemplo, crea dos objetos Receta con los mismos valores de propiedad, al comparar los dos objetos con el operador == se obtendrá el resultado verdadero, porque los mismos datos se almacenan en ellos:
val r1 = Recipe("Chicken Bhuna", false) val r2 = Recipe("Chicken Bhuna", false)
r1 y r2 se consideran "iguales" porque dos objetos Receta contienen los mismos datos.
Además de la nueva implementación de la función equals heredada de la superclase Any, clases de datos
también anula las funciones hashCode y toString. Veamos cómo se implementan estas funciones.
Los objetos de clase redefinen su comportamiento heredado
Para trabajar con datos, una clase de datos necesita objetos, por lo que proporciona automáticamente las siguientes implementaciones para las funciones equals, hashCode y toString heredadas de la superclase Any:
La función igual compara valores de propiedad
Al definir una clase de datos, su función igual (y, por lo tanto, el operador ==) aún devuelve verdadero si los enlaces apuntan al mismo objeto. Pero también devuelve verdadero si los objetos tienen los mismos valores de propiedad definidos en el constructor:
val r1 = Recipe("Chicken Bhuna", false) val r2 = Recipe("Chicken Bhuna", false) println(r1.equals(r2)) true
Los objetos de datos se consideran iguales si sus propiedades contienen el mismo valor.
Para objetos iguales, se devuelven los mismos valores de hashCode
Si dos objetos de datos se consideran iguales (en otras palabras, tienen los mismos valores de propiedad), la función hashCode devuelve el mismo valor para estos objetos:
val r1 = Recipe("Chicken Bhuna", false) val r2 = Recipe("Chicken Bhuna", false) println(r1.hashCode()) println(r2.hashCode())
241131113
241131113toString devuelve los valores de todas las propiedades
Finalmente, la función toString ya no devuelve el nombre de la clase, seguido de un número, sino que devuelve una cadena útil con los valores de todas las propiedades definidas en el constructor de la clase de datos:
val r1 = Recipe("Chicken Bhuna", false) println(r1.toString()) Recipe(title=Chicken Bhuna, isVegetarian=false)
Además de anular las funciones heredadas de la superclase Any, la clase de datos también proporciona herramientas adicionales que proporcionan un trabajo más eficiente con datos, por ejemplo, la capacidad de copiar objetos de datos. Veamos cómo funcionan estas herramientas.
Copiar objetos de datos con la función de copia
Si necesita crear una copia del objeto de datos cambiando algunas de sus propiedades, pero dejando las otras propiedades en su estado original, use la función de copia. Para hacer esto, se llama a la función para el objeto que desea copiar y se le pasan los nombres de todas las propiedades mutables con nuevos valores.
Supongamos que tiene un objeto Receta llamado r1, que se define en el código de esta manera:
val r1 = Recipe("Thai Curry", false)
Si desea crear una copia del objeto Receta, reemplazando el valor de la propiedad isVegetarian por verdadero, esto se hace así:
En esencia, esto significa "crear una copia del objeto r1, cambiar el valor de su propiedad isVegetarian a verdadero y asignar un nuevo objeto a una variable llamada r2". Esto crea una nueva copia del objeto y el objeto original permanece sin cambios.
Además de la función de copia, las clases de datos también proporcionan un conjunto de funciones para dividir un objeto de datos en un conjunto de valores de sus propiedades; este proceso se denomina desestructuración. Veamos cómo se hace esto.
Las clases de datos definen las funciones componenteN ...
Al definir una clase de datos, el compilador agrega automáticamente a la clase un conjunto de funciones que pueden usarse como un mecanismo alternativo para acceder a los valores de las propiedades del objeto. Estas funciones se conocen con el nombre general de las funciones componenteN, donde N es el número de propiedades que se recuperarán (en el orden de la declaración).
Para ver cómo funcionan las funciones de componentes, suponga que tiene el siguiente objeto Receta:
val r = Recipe("Chicken Bhuna", false)
Si desea obtener el valor de la primera propiedad del objeto (propiedad de título), puede llamar a la función componente1 () del objeto para esto:
val title = r.component1()
component1 () devuelve la referencia que está contenida en la primera propiedad definida en el constructor de la clase de datos.
La función hace lo mismo que el siguiente código:
val title = r.title
El código con la función es más universal. ¿Por qué las funciones ComponentN son tan útiles en las clases de datos?
... diseñado para reestructurar objetos de datos
Las funciones generalizadas de componentN son útiles principalmente porque proporcionan una manera simple y conveniente de dividir un objeto de datos en valores de propiedad, o de destruirlo.
Suponga que desea tomar los valores de las propiedades de un objeto Receta y asignar el valor de cada una de sus propiedades a una variable separada. En lugar de código
val title = r.title val vegetarian = r.isVegetarian
con el procesamiento secuencial de cada propiedad, puede usar el siguiente código:
val (title, vegetarian) = r
Asigna título a la primera propiedad r y vegetariano a la segunda propiedad.
Este código significa "crear dos variables, título y vegetariano, y asignar el valor de una de las propiedades r de cada variable". Hace lo mismo que el siguiente fragmento.
val title = r.component1() val vegetarian = r.component2()
pero resulta más compacto
El operador === siempre verifica si dos variables se refieren al mismo objeto.Si desea verificar si dos variables se refieren al mismo objeto independientemente de su tipo, use el operador === en lugar de ==. El operador === da el resultado verdadero si (y solo si) cuando dos variables contienen una referencia a un objeto real. Si tiene dos variables, x e y, y la siguiente expresión:
x === y
da el resultado verdadero, entonces sabes que las variables x e y deben referirse al mismo objeto.
A diferencia del operador ==, el comportamiento del operador === es independiente de la función igual. El operador === siempre se comporta igual independientemente del tipo de clase.
Ahora que ha aprendido a crear y usar clases de datos, cree un proyecto para el código de receta.
Crear un proyecto de recetas
Cree un nuevo proyecto de Kotlin para la JVM y asígnele el nombre "Recetas". Luego crea un nuevo
Archivo Kotlin llamado Recipes.kt: seleccione la carpeta src, abra el menú Archivo y seleccione el comando
Nuevo → Archivo / Clase Kotlin. Ingrese el nombre de archivo "Recetas" y seleccione la opción Archivo en el grupo Tipo.
Agregamos una nueva clase de datos al proyecto llamada Receta y creamos objetos de datos de Receta. Debajo está el código. Actualice su versión de Recipes.kt y ajústela a la nuestra:
data class Recipe(val title: String, val isVegetarian: Boolean) ( {} , .) fun main(args: Array<String>) { val r1 = Recipe("Thai Curry", false) val r2 = Recipe("Thai Curry", false) val r3 = r1.copy(title = "Chicken Bhuna") ( r1 title) println("r1 hash code: ${r1.hashCode()}") println("r2 hash code: ${r2.hashCode()}") println("r3 hash code: ${r3.hashCode()}") println("r1 toString: ${r1.toString()}") println("r1 == r2? ${r1 == r2}") println("r1 === r2? ${r1 === r2}") println("r1 == r3? ${r1 == r3}") val (title, vegetarian) = r1 ( r1) println("title is $title and vegetarian is $vegetarian") }
Cuando ejecute su código, aparecerá el siguiente texto en la ventana de salida del IDE:
r1 hash code: -135497891 r2 hash code: -135497891 r3 hash code: 241131113 r1 toString: Recipe(title=Thai Curry, isVegetarian=false) r1 == r2? true r1 === r2? false r1 == r3? false title is Thai Curry and vegetarian is false
»Se puede encontrar más información sobre el libro en
el sitio web del editor»
Contenidos»
Extracto
25% de descuento en el cupón para Khabrozhitel -
KotlinTras el pago de la versión en papel del libro, se envía un libro electrónico por correo electrónico.