Este codelab es parte del curso de capacitación de Kotlin para programadores. Aprovecharás al máximo este curso si trabajas con los codelabs de forma secuencial. Según tus conocimientos, es posible que puedas cambiar algunas secciones. Este curso está dirigido a programadores que conocen un lenguaje orientado a objetos y quieren aprender Kotlin.
Introducción
En este codelab, se te presentarán varias funciones útiles diferentes en Kotlin, como pares, colecciones y funciones de extensión.
En lugar de crear una sola app de ejemplo, las lecciones de este curso están diseñadas para ampliar tus conocimientos, pero son semiindependientes entre sí para que puedas revisar las secciones con las que ya estás familiarizado. Para unirlos, muchos de los ejemplos usan un tema de acuario. Si quieres ver la historia completa del acuario, consulta el curso de Udacity Kotlin Bootcamp for Programmers.
Conocimientos que ya deberías tener
- La sintaxis de las funciones, las clases y los métodos de Kotlin
- Cómo trabajar con el REPL (bucle de lectura, evaluación e impresión) de Kotlin en IntelliJ IDEA
- Cómo crear una clase nueva en IntelliJ IDEA y ejecutar un programa
Qué aprenderás
- Cómo trabajar con pares y tríos
- Más información sobre las colecciones
- Cómo definir y usar constantes
- Cómo escribir funciones de extensión
Actividades
- Aprende sobre pares, tríos y mapas de hash en el REPL
- Aprende diferentes formas de organizar las constantes
- Escribe una función de extensión y una propiedad de extensión
En esta tarea, aprenderás sobre los pares y las tríadas, y cómo desestructurarlos. Los pares y las tríadas son clases de datos prediseñadas para 2 o 3 elementos genéricos. Por ejemplo, esto puede ser útil para que una función devuelva más de un valor.
Supongamos que tienes un List
de peces y una función isFreshWater()
para verificar si el pez es de agua dulce o salada. List.partition()
devuelve dos listas: una con los elementos en los que la condición es true
y otra con los elementos en los que la condición es false
.
val twoLists = fish.partition { isFreshWater(it) }
println("freshwater: ${twoLists.first}")
println("saltwater: ${twoLists.second}")
Paso 1: Crea algunos pares y tríos
- Abre el REPL (Tools > Kotlin > Kotlin REPL).
- Crea un par que asocie un equipo con su uso y, luego, imprime los valores. Puedes crear un par con una expresión que conecte dos valores, como dos cadenas, con la palabra clave
to
y, luego, usar.first
o.second
para hacer referencia a cada valor.
val equipment = "fish net" to "catching fish"
println("${equipment.first} used for ${equipment.second}")
⇒ fish net used for catching fish
- Crea una tripleta y muéstrala con
toString()
. Luego, conviértela en una lista contoList()
. Creas una tripleta conTriple()
y 3 valores. Usa.first
,.second
y.third
para hacer referencia a cada valor.
val numbers = Triple(6, 9, 42)
println(numbers.toString())
println(numbers.toList())
⇒ (6, 9, 42) [6, 9, 42]
En los ejemplos anteriores, se usa el mismo tipo para todas las partes del par o la tripleta, pero no es obligatorio. Las partes pueden ser una cadena, un número o una lista, por ejemplo, incluso otro par o trío.
- Crea un par en el que la primera parte del par sea en sí misma un par.
val equipment2 = ("fish net" to "catching fish") to "equipment"
println("${equipment2.first} is ${equipment2.second}\n")
println("${equipment2.first.second}")
⇒ (fish net, catching fish) is equipment ⇒ catching fish
Paso 2: Desestructura algunos pares y tríos
La separación de pares y tríos en sus partes se denomina desestructuración. Asigna el par o la tripleta a la cantidad adecuada de variables, y Kotlin asignará el valor de cada parte en orden.
- Desestructura un par e imprime los valores.
val equipment = "fish net" to "catching fish"
val (tool, use) = equipment
println("$tool is used for $use")
⇒ fish net is used for catching fish
- Desestructura una tupla y, luego, imprime los valores.
val numbers = Triple(6, 9, 42)
val (n1, n2, n3) = numbers
println("$n1 $n2 $n3")
⇒ 6 9 42
Ten en cuenta que la desestructuración de pares y tríos funciona de la misma manera que con las clases de datos, lo que se abordó en un codelab anterior.
En esta tarea, aprenderás más sobre las colecciones, incluidas las listas, y un nuevo tipo de colección, los mapas de hash.
Paso 1: Obtén más información sobre las listas
- En una lección anterior, se presentaron las listas y las listas mutables. Son una estructura de datos muy útil, por lo que Kotlin proporciona varias funciones integradas para las listas. Revisa esta lista parcial de funciones para listas. Puedes encontrar listados completos en la documentación de Kotlin para
List
yMutableList
.
Función | Purpose |
| Agrega un elemento a la lista mutable. |
| Quita un elemento de una lista mutable. |
| Devuelve una copia de la lista con los elementos en orden inverso. |
| Devuelve |
| Devuelve parte de la lista, desde el primer índice hasta el segundo, sin incluirlo. |
- Mientras sigues trabajando en el REPL, crea una lista de números y llama a
sum()
en ella. Esto suma todos los elementos.
val list = listOf(1, 5, 3, 4)
println(list.sum())
⇒ 13
- Crea una lista de cadenas y suma los elementos de la lista.
val list2 = listOf("a", "bbb", "cc")
println(list2.sum())
⇒ error: none of the following functions can be called with the arguments supplied:
- Si el elemento no es algo que
List
sabe cómo sumar directamente, como una cadena, puedes especificar cómo sumarlo con.sumBy()
con una función lambda, por ejemplo, para sumar según la longitud de cada cadena. El nombre predeterminado para un argumento lambda esit
, y aquíit
hace referencia a cada elemento de la lista a medida que se recorre.
val list2 = listOf("a", "bbb", "cc")
println(list2.sumBy { it.length })
⇒ 6
- Hay mucho más que puedes hacer con las listas. Una forma de ver la funcionalidad disponible es crear una lista en IntelliJ IDEA, agregar el punto y, luego, observar la lista de autocompletado en la sugerencia. Esto funciona para cualquier objeto. Pruébalo con una lista.
- Elige
listIterator()
en la lista, luego recorre la lista con una instrucciónfor
y, por último, imprime todos los elementos separados por espacios.
val list2 = listOf("a", "bbb", "cc")
for (s in list2.listIterator()) {
println("$s ")
}
⇒ a bbb cc
Paso 2: Prueba los mapas hash
En Kotlin, puedes asignar casi cualquier cosa a cualquier otra cosa con hashMapOf()
. Los mapas de hash son como una lista de pares, en la que el primer valor actúa como clave.
- Crea un mapa de hash que coincida con los síntomas (las claves) y las enfermedades de los peces (los valores).
val cures = hashMapOf("white spots" to "Ich", "red sores" to "hole disease")
- Luego, puedes recuperar el valor de la enfermedad según la clave del síntoma con
get()
o, incluso, con corchetes[]
.
println(cures.get("white spots"))
⇒ Ich
println(cures["red sores"])
⇒ hole disease
- Intenta especificar un síntoma que no esté en el mapa.
println(cures["scale loss"])
⇒ null
Si una clave no está en el mapa, intentar devolver la enfermedad coincidente devuelve null
. Según los datos del mapa, es posible que no haya coincidencias para una posible clave. Para esos casos, Kotlin proporciona la función getOrDefault()
.
- Intenta buscar una clave que no coincida con
getOrDefault()
.
println(cures.getOrDefault("bloating", "sorry, I don't know"))
⇒ sorry, I don't know
Si necesitas hacer algo más que devolver un valor, Kotlin proporciona la función getOrElse()
.
- Cambia tu código para usar
getOrElse()
en lugar degetOrDefault()
.
println(cures.getOrElse("bloating") {"No cure for this"})
⇒ No cure for this
En lugar de devolver un valor predeterminado simple, se ejecuta el código que se encuentre entre las llaves {}
. En el ejemplo, else
simplemente devuelve una cadena, pero podría ser tan sofisticado como encontrar una página web con una cura y devolverla.
Al igual que mutableListOf
, también puedes crear un mutableMapOf
. Un mapa mutable te permite agregar y quitar elementos. Mutable solo significa que se puede cambiar, mientras que inmutable significa que no se puede cambiar.
- Crea un mapa de inventario que se pueda modificar y que asigne una cadena de equipo a la cantidad de elementos. Crea el tanque con una red de pesca, luego agrega 3 esponjas para tanques al inventario con
put()
y quita la red de pesca conremove()
.
val inventory = mutableMapOf("fish net" to 1)
inventory.put("tank scrubber", 3)
println(inventory.toString())
inventory.remove("fish net")
println(inventory.toString())
⇒ {fish net=1, tank scrubber=3}{tank scrubber=3}
En esta tarea, aprenderás sobre las constantes en Kotlin y las diferentes formas de organizarlas.
Paso 1: Obtén información sobre const y val
- En el REPL, intenta crear una constante numérica. En Kotlin, puedes crear constantes de nivel superior y asignarles un valor en el tiempo de compilación con
const val
.
const val rocks = 3
El valor se asigna y no se puede cambiar, lo que se parece mucho a declarar un val
normal. Entonces, ¿cuál es la diferencia entre const val
y val
? El valor de const val
se determina en el momento de la compilación, mientras que el valor de val
se determina durante la ejecución del programa, lo que significa que una función puede asignar val
en el tiempo de ejecución.
Esto significa que se le puede asignar un valor a val
desde una función, pero no a const val
.
val value1 = complexFunctionCall() // OK
const val CONSTANT1 = complexFunctionCall() // NOT ok
Además, const val
solo funciona en el nivel superior y en las clases singleton declaradas con object
, no con clases normales. Puedes usarlo para crear un archivo o un objeto singleton que solo contenga constantes y, luego, importarlos según sea necesario.
object Constants {
const val CONSTANT2 = "object constant"
}
val foo = Constants.CONSTANT2
Paso 2: Crea un objeto complementario
Kotlin no tiene un concepto de constantes a nivel de la clase.
Para definir constantes dentro de una clase, debes incluirlas en objetos complementarios declarados con la palabra clave companion
. El objeto complementario es básicamente un objeto singleton dentro de la clase.
- Crea una clase con un objeto complementario que contenga una constante de cadena.
class MyClass {
companion object {
const val CONSTANT3 = "constant in companion"
}
}
La diferencia básica entre los objetos complementarios y los objetos normales es la siguiente:
- Los objetos complementarios se inicializan desde el constructor estático de la clase contenedora, es decir, se crean cuando se crea el objeto.
- Los objetos normales se inicializan de forma diferida en el primer acceso a ese objeto, es decir, cuando se usan por primera vez.
Hay más, pero todo lo que necesitas saber por ahora es que debes incluir las constantes en clases dentro de un objeto complementario.
En esta tarea, aprenderás a extender el comportamiento de las clases. Es muy común escribir funciones de utilidad para extender el comportamiento de una clase. Kotlin proporciona una sintaxis conveniente para declarar estas funciones de utilidad: funciones de extensión.
Las funciones de extensión te permiten agregar funciones a una clase existente sin tener que acceder a su código fuente. Por ejemplo, puedes declararlos en un archivo Extensions.kt que forme parte de tu paquete. En realidad, esto no modifica la clase, pero te permite usar la notación de puntos cuando llamas a la función en objetos de esa clase.
Paso 1: Escribe una función de extensión
- Mientras sigues trabajando en el REPL, escribe una función de extensión simple,
hasSpaces()
, para verificar si una cadena contiene espacios. El nombre de la función tiene el prefijo de la clase en la que opera. Dentro de la función,this
hace referencia al objeto en el que se llama, yit
hace referencia al iterador en la llamada afind()
.
fun String.hasSpaces(): Boolean {
val found = this.find { it == ' ' }
return found != null
}
println("Does it have spaces?".hasSpaces())
⇒ true
- Puedes simplificar la función
hasSpaces()
. No se necesitathis
de forma explícita, y la función se puede reducir a una sola expresión y devolverse, por lo que tampoco se necesitan las llaves{}
que la rodean.
fun String.hasSpaces() = find { it == ' ' } != null
Paso 2: Conoce las limitaciones de las extensiones
Las funciones de extensión solo tienen acceso a la API pública de la clase que extienden. No se puede acceder a las variables que son private
.
- Intenta agregar funciones de extensión a una propiedad marcada como
private
.
class AquariumPlant(val color: String, private val size: Int)
fun AquariumPlant.isRed() = color == "red" // OK
fun AquariumPlant.isBig() = size > 50 // gives error
⇒ error: cannot access 'size': it is private in 'AquariumPlant'
- Examina el siguiente código y determina qué imprimirá.
open class AquariumPlant(val color: String, private val size: Int)
class GreenLeafyPlant(size: Int) : AquariumPlant("green", size)
fun AquariumPlant.print() = println("AquariumPlant")
fun GreenLeafyPlant.print() = println("GreenLeafyPlant")
val plant = GreenLeafyPlant(size = 10)
plant.print()
println("\n")
val aquariumPlant: AquariumPlant = plant
aquariumPlant.print() // what will it print?
⇒ GreenLeafyPlant AquariumPlant
plant.print()
copias GreenLeafyPlant
. Es posible que esperes que aquariumPlant.print()
también imprima GreenLeafyPlant
, ya que se le asignó el valor de plant
. Sin embargo, el tipo se resuelve en el tiempo de compilación, por lo que se imprime AquariumPlant
.
Paso 3: Agrega una propiedad de extensión
Además de las funciones de extensión, Kotlin también te permite agregar propiedades de extensión. Al igual que con las funciones de extensión, debes especificar la clase que extiendes, seguida de un punto y el nombre de la propiedad.
- Mientras sigues trabajando en el REPL, agrega una propiedad de extensión
isGreen
aAquariumPlant
, que estrue
si el color es verde.
val AquariumPlant.isGreen: Boolean
get() = color == "green"
Se puede acceder a la propiedad isGreen
como a cualquier otra propiedad. Cuando se accede a ella, se llama al método get de isGreen
para obtener el valor.
- Imprime la propiedad
isGreen
para la variableaquariumPlant
y observa el resultado.
aquariumPlant.isGreen
⇒ res4: kotlin.Boolean = true
Paso 4: Obtén información sobre los receptores que admiten valores nulos
La clase que extiendes se llama receptor, y es posible hacer que esa clase sea anulable. Si lo haces, la variable this
que se usa en el cuerpo puede ser null
, así que asegúrate de probarla. Te convendría tomar un receptor anulable si esperas que los llamadores quieran llamar a tu método de extensión en variables anulables o si quieres proporcionar un comportamiento predeterminado cuando tu función se aplique a null
.
- Aún en el REPL, define un método
pull()
que tome un receptor que admite valores nulos. Esto se indica con un signo de interrogación?
después del tipo y antes del punto. Dentro del cuerpo, puedes probar sithis
no esnull
usando questionmark-dot-apply?.apply.
fun AquariumPlant?.pull() {
this?.apply {
println("removing $this")
}
}
val plant: AquariumPlant? = null
plant.pull()
- En este caso, no se genera ningún resultado cuando ejecutas el programa. Como
plant
esnull
, no se llama alprintln()
interno.
Las funciones de extensión son muy potentes, y la mayor parte de la biblioteca estándar de Kotlin se implementa como funciones de extensión.
En esta lección, aprendiste más sobre las colecciones y las constantes, y probaste el poder de las funciones y propiedades de extensión.
- Los pares y las tuplas se pueden usar para devolver más de un valor desde una función. Por ejemplo:
val twoLists = fish.partition { isFreshWater(it) }
- Kotlin tiene muchas funciones útiles para
List
, comoreversed()
,contains()
ysubList()
. - Se puede usar un
HashMap
para asignar claves a valores. Por ejemplo:val cures = hashMapOf("white spots" to "Ich", "red sores" to "hole disease")
- Declara constantes de tiempo de compilación con la palabra clave
const
. Puedes colocarlos en el nivel superior, organizarlos en un objeto singleton o colocarlos en un objeto complementario. - Un objeto complementario es un objeto singleton dentro de una definición de clase, definido con la palabra clave
companion
. - Las funciones y propiedades de extensión pueden agregar funcionalidad a una clase. Por ejemplo:
fun String.hasSpaces() = find { it == ' ' } != null
- Un receptor anulable te permite crear extensiones en una clase que puede ser
null
. El operador?.
se puede combinar conapply
para verificarnull
antes de ejecutar el código. Por ejemplo:this?.apply { println("removing $this") }
Documentación de Kotlin
Si deseas obtener más información sobre algún tema de este curso o si te quedas atascado, https://kotlinlang.org es el mejor punto de partida.
Pair
Triple
List
MutableList
HashMap
- Objetos complementarios
- Extensiones
- Receptor que acepta valores nulos
Instructivos de Kotlin
El sitio web https://try.kotlinlang.org incluye instructivos enriquecidos llamados Kotlin Koans, un intérprete basado en la Web y un conjunto completo de documentación de referencia con ejemplos.
Curso de Udacity
Para ver el curso de Udacity sobre este tema, consulta Capacitación de Kotlin para programadores.
IntelliJ IDEA
Encontrarás la documentación de IntelliJ IDEA en el sitio web de JetBrains.
En esta sección, se enumeran las posibles actividades para el hogar para los alumnos que trabajan en este codelab como parte de un curso dirigido por un instructor. Depende del instructor hacer lo siguiente:
- Si es necesario, asigna una tarea.
- Comunicarles a los alumnos cómo enviar las actividades para el hogar.
- Califica las actividades para el hogar.
Los instructores pueden usar estas sugerencias en la medida que quieran y deben asignar cualquier otra actividad para el hogar que consideren apropiada.
Si estás trabajando en este codelab por tu cuenta, usa estas actividades para el hogar para probar tus conocimientos.
Responde estas preguntas:
Pregunta 1
¿Cuál de las siguientes opciones devuelve una copia de una lista?
▢ add()
▢ remove()
▢ reversed()
▢ contains()
Pregunta 2
¿Cuál de estas funciones de extensión en class AquariumPlant(val color: String, val size: Int, private val cost: Double, val leafy: Boolean)
generará un error de compilación?
▢ fun AquariumPlant.isRed() = color == "red"
▢ fun AquariumPlant.isBig() = size > 45
▢ fun AquariumPlant.isExpensive() = cost > 10.00
▢ fun AquariumPlant.isNotLeafy() = leafy == false
Pregunta 3
¿Cuál de las siguientes opciones no es un lugar donde puedes definir constantes con const val
?
▢ en el nivel superior de un archivo
▢ En clases regulares
▢ en objetos singleton
▢ en objetos complementarios
Continúa con la siguiente lección:
Para obtener una descripción general del curso, incluidos los vínculos a otros codelabs, consulta "Capacitación de Kotlin para programadores: Bienvenido al curso".