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
Este es el último codelab de la Capacitación de Kotlin. En este codelab, aprenderás sobre las anotaciones y los saltos etiquetados. Aprenderás sobre lambdas y funciones de orden superior, que son partes clave de Kotlin. También obtendrás más información sobre las funciones intercaladas y las interfaces de Single Abstract Method (SAM). Por último, obtendrás más información sobre la biblioteca estándar de Kotlin.
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 crear una clase nueva en IntelliJ IDEA y ejecutar un programa
- Conceptos básicos sobre lambdas y funciones de orden superior
Qué aprenderás
- Conceptos básicos de las anotaciones
- Cómo usar los descansos etiquetados
- Más información sobre las funciones de orden superior
- Acerca de las interfaces de método único abstracto (SAM)
- Acerca de la biblioteca estándar de Kotlin
Actividades
- Crea una anotación simple.
- Usa un corte etiquetado.
- Revisar las funciones lambda en Kotlin
- Usar y crear funciones de orden superior
- Llama a algunas interfaces de método único abstracto.
- Usar algunas funciones de la biblioteca estándar de Kotlin
Las anotaciones son una forma de adjuntar metadatos al código y no son específicas de Kotlin. El compilador lee las anotaciones y las usa para generar código o lógica. Muchos frameworks, como Ktor y Kotlinx, así como Room, usan anotaciones para configurar cómo se ejecutan y cómo interactúan con tu código. Es poco probable que encuentres anotaciones hasta que comiences a usar frameworks, pero es útil saber cómo leer una anotación.
También hay anotaciones disponibles a través de la biblioteca estándar de Kotlin que controlan la forma en que se compila el código. Son muy útiles si exportas código Kotlin a Java, pero, de lo contrario, no los necesitas con tanta frecuencia.
Las anotaciones se colocan justo antes de lo que se anota, y la mayoría de las cosas se pueden anotar: clases, funciones, métodos y hasta estructuras de control. Algunas anotaciones pueden tomar argumentos.
Aquí tienes un ejemplo de algunas anotaciones.
@file:JvmName("InteropFish")
class InteropFish {
companion object {
@JvmStatic fun interop()
}
}Esto indica que el nombre exportado de este archivo es InteropFish con la anotación JvmName. La anotación JvmName toma un argumento de "InteropFish". En el objeto complementario, @JvmStatic le indica a Kotlin que haga de interop() una función estática en InteropFish.
También puedes crear tus propias anotaciones, pero esto es más útil si escribes una biblioteca que necesita información particular sobre las clases en el tiempo de ejecución, es decir, reflexión.
Paso 1: Crea un paquete y un archivo nuevos
- En src, crea un paquete nuevo,
example. - En example, crea un archivo Kotlin nuevo,
Annotations.kt.
Paso 2: Crea tu propia anotación
- En
Annotations.kt, crea una clasePlantcon dos métodos,trim()yfertilize().
class Plant {
fun trim(){}
fun fertilize(){}
}- Crea una función que imprima todos los métodos de una clase. Usa
::classpara obtener información sobre una clase en el tiempo de ejecución. UsadeclaredMemberFunctionspara obtener una lista de los métodos de una clase. (Para acceder a esto, debes importarkotlin.reflect.full.*).
import kotlin.reflect.full.* // required import
class Plant {
fun trim(){}
fun fertilize(){}
}
fun testAnnotations() {
val classObj = Plant::class
for (m in classObj.declaredMemberFunctions) {
println(m.name)
}
}- Crea una función
main()para llamar a tu rutina de prueba. Ejecuta tu programa y observa el resultado.
fun main() {
testAnnotations()
}⇒ trim fertilize
- Crea una anotación simple,
ImAPlant.
annotation class ImAPlantEsto no hace nada más que indicar que está anotado.
- Agrega la anotación delante de tu clase
Plant.
@ImAPlant class Plant{
...
}- Cambia
testAnnotations()para imprimir todas las anotaciones de una clase. Usaannotationspara obtener todas las anotaciones de una clase. Ejecuta el programa y observa el resultado.
fun testAnnotations() {
val plantObject = Plant::class
for (a in plantObject.annotations) {
println(a.annotationClass.simpleName)
}
}⇒ ImAPlant
- Cambia
testAnnotations()para encontrar la anotaciónImAPlant. UsafindAnnotation()para encontrar una anotación específica. Ejecuta el programa y observa el resultado.
fun testAnnotations() {
val plantObject = Plant::class
val myAnnotationObject = plantObject.findAnnotation<ImAPlant>()
println(myAnnotationObject)
}
⇒ @example.ImAPlant()
Paso 3: Crea una anotación segmentada
Las anotaciones pueden segmentarse para los métodos get o set. Cuando lo hagas, puedes aplicarlos con el prefijo @get: o @set:. Esto sucede a menudo cuando se usan frameworks con anotaciones.
- Declara dos anotaciones,
OnGet, que solo se pueden aplicar a los métodos get de propiedades, yOnSet, que solo se pueden aplicar a los métodos set de propiedades. Usa@Target(AnnotationTarger.PROPERTY_GETTER)oPROPERTY_SETTERen cada uno.
annotation class ImAPlant
@Target(AnnotationTarget.PROPERTY_GETTER)
annotation class OnGet
@Target(AnnotationTarget.PROPERTY_SETTER)
annotation class OnSet
@ImAPlant class Plant {
@get:OnGet
val isGrowing: Boolean = true
@set:OnSet
var needsFood: Boolean = false
}Las anotaciones son muy útiles para crear bibliotecas que inspeccionan elementos tanto en el tiempo de ejecución como, a veces, en el tiempo de compilación. Sin embargo, el código de aplicación típico solo usa las anotaciones que proporcionan los frameworks.
Kotlin tiene varias formas de controlar el flujo. Ya conoces return, que regresa de una función a su función envolvente. Usar un break es como usar un return, pero para bucles.
Kotlin te brinda control adicional sobre los bucles con lo que se denomina interrupción etiquetada. Un break calificado con una etiqueta salta al punto de ejecución inmediatamente después del bucle marcado con esa etiqueta. Esto es particularmente útil cuando se trabaja con bucles anidados.
Cualquier expresión en Kotlin se puede marcar con una etiqueta. Las etiquetas tienen la forma de un identificador seguido del signo @.
- En
Annotations.kt, prueba una interrupción etiquetada saliendo de un bucle interno.
fun labels() {
outerLoop@ for (i in 1..100) {
print("$i ")
for (j in 1..100) {
if (i > 10) break@outerLoop // breaks to outer loop
}
}
}
fun main() {
labels()
}- Ejecuta tu programa y observa el resultado.
⇒ 1 2 3 4 5 6 7 8 9 10 11
Del mismo modo, puedes usar un continue etiquetado. En lugar de salir del bucle etiquetado, la instrucción continue etiquetada pasa a la siguiente iteración del bucle.
Las lambdas son funciones anónimas, es decir, funciones sin nombre. Puedes asignarlos a variables y pasarlos como argumentos a funciones y métodos. Son extremadamente útiles.
Paso 1: Crea una función lambda simple
- Inicia el REPL en IntelliJ IDEA, Tools > Kotlin > Kotlin REPL.
- Crea una expresión lambda con un argumento,
dirty: Int, que realice un cálculo dividiendodirtyentre 2. Asigna la expresión lambda a una variable,waterFilter.
val waterFilter = { dirty: Int -> dirty / 2 }- Llama a
waterFiltery pasa un valor de 30.
waterFilter(30)⇒ res0: kotlin.Int = 15
Paso 2: Crea una función lambda de filtro
- Aún en el REPL, crea una clase de datos,
Fish, con una propiedad,name.
data class Fish(val name: String)- Crea una lista de 3
Fish, con los nombres Flipper, Moby Dick y Dory.
val myFish = listOf(Fish("Flipper"), Fish("Moby Dick"), Fish("Dory"))- Agrega un filtro para verificar los nombres que contienen la letra "i".
myFish.filter { it.name.contains("i")}
⇒ res3: kotlin.collections.List<Line_1.Fish> = [Fish(name=Flipper), Fish(name=Moby Dick)]
En la expresión lambda, it hace referencia al elemento de lista actual, y el filtro se aplica a cada elemento de la lista a su vez.
- Aplica
joinString()al resultado, usando", "como separador.
myFish.filter { it.name.contains("i")}.joinToString(", ") { it.name }
⇒ res4: kotlin.String = Flipper, Moby Dick
La función joinToString() crea una cadena uniendo los nombres filtrados, separados por la cadena especificada. Es una de las muchas funciones útiles integradas en la biblioteca estándar de Kotlin.
Pasar una función lambda o alguna otra función como argumento a una función crea una función de orden superior. El filtro anterior es un ejemplo simple de esto. filter() es una función, y le pasas una lambda que especifica cómo procesar cada elemento de la lista.
Escribir funciones de orden superior con lambdas de extensión es una de las partes más avanzadas del lenguaje Kotlin. Lleva un tiempo aprender a escribirlos, pero son muy convenientes de usar.
Paso 1: Crea una clase nueva
- Dentro del paquete example, crea un nuevo archivo de Kotlin,
Fish.kt. - En
Fish.kt, crea una clase de datosFishcon una propiedad,name.
data class Fish (var name: String)- Crea una función
fishExamples(). EnfishExamples(), crea un pez llamado"splashy", todo en minúsculas.
fun fishExamples() {
val fish = Fish("splashy") // all lowercase
}- Crea una función
main()que llame afishExamples().
fun main () {
fishExamples()
}- Compila y ejecuta tu programa haciendo clic en el triángulo verde a la izquierda de
main(). Aún no hay resultados.
Paso 2: Usa una función de orden superior
La función with() te permite hacer una o más referencias a un objeto o propiedad de una manera más compacta. Apps que usan this En realidad, with() es una función de orden superior, y en la lambda especificas qué hacer con el objeto proporcionado.
- Usa
with()para poner en mayúscula el nombre del pez enfishExamples(). Dentro de las llaves,thishace referencia al objeto que se pasó awith().
fun fishExamples() {
val fish = Fish("splashy") // all lowercase
with (fish.name) {
this.capitalize()
}
}- No hay ningún resultado, por lo que debes agregar un
println()alrededor. Además, elthises implícito y no es necesario, por lo que puedes quitarlo.
fun fishExamples() {
val fish = Fish("splashy") // all lowercase
with (fish.name) {
println(capitalize())
}
}⇒ Splashy
Paso 3: Crea una función de orden superior
En segundo plano, with() es una función de orden superior. Para ver cómo funciona, puedes crear tu propia versión muy simplificada de with() que solo funcione para cadenas.
- En
Fish.kt, define una función,myWith(), que tome dos argumentos. Los argumentos son el objeto sobre el que se opera y una función que define la operación. La convención para el nombre del argumento con la función esblock. En este caso, esa función no devuelve nada, lo que se especifica conUnit.
fun myWith(name: String, block: String.() -> Unit) {}Dentro de myWith(), block() ahora es una función de extensión de String. La clase que se extiende a menudo se denomina objeto receptor. Por lo tanto, name es el objeto receptor en este caso.
- En el cuerpo de
myWith(), aplica la función pasada,block(), al objeto receptor,name.
fun myWith(name: String, block: String.() -> Unit) {
name.block()
}- En
fishExamples(), reemplazawith()pormyWith().
fun fishExamples() {
val fish = Fish("splashy") // all lowercase
myWith (fish.name) {
println(capitalize())
}
}fish.name es el argumento del nombre y println(capitalize()) es la función de bloque.
- Ejecuta el programa, y funcionará como antes.
⇒ Splashy
Paso 4: Explora más extensiones integradas
La expresión lambda de extensión with() es muy útil y forma parte de la biblioteca estándar de Kotlin. Estos son algunos de los otros que podrían ser útiles: run(), apply() y let().
La función run() es una extensión que funciona con todos los tipos. Toma una expresión lambda como argumento y devuelve el resultado de ejecutarla.
- En
fishExamples(), llama arun()enfishpara obtener el nombre.
fish.run {
name
}Esto solo devuelve la propiedad name. Podrías asignarlo a una variable o imprimirlo. En realidad, este no es un ejemplo útil, ya que podrías acceder a la propiedad, pero run() puede ser útil para expresiones más complicadas.
La función apply() es similar a run(), pero devuelve el objeto cambiado al que se aplicó en lugar del resultado de la expresión lambda. Esto puede ser útil para llamar a métodos en un objeto recién creado.
- Haz una copia de
fishy llama aapply()para establecer el nombre de la copia nueva.
val fish2 = Fish(name = "splashy").apply {
name = "sharky"
}
println(fish2.name)
⇒ sharky
La función let() es similar a apply(), pero devuelve una copia del objeto con los cambios. Esto puede ser útil para encadenar manipulaciones.
- Usa
let()para obtener el nombre defish, ponerlo en mayúsculas, concatenarle otra cadena, obtener la longitud de ese resultado, sumarle 31 a la longitud y, luego, imprimir el resultado.
println(fish.let { it.name.capitalize()}
.let{it + "fish"}
.let{it.length}
.let{it + 31})⇒ 42
En este ejemplo, el tipo de objeto al que hace referencia it es Fish, luego String, luego String de nuevo y, finalmente, Int.
- Imprime
fishdespués de llamar alet()y verás que no cambió.
println(fish.let { it.name.capitalize()}
.let{it + "fish"}
.let{it.length}
.let{it + 31})
println(fish)⇒ 42 Fish(name=splashy)
Las lambdas y las funciones de orden superior son muy útiles, pero debes saber que las lambdas son objetos. Una expresión lambda es una instancia de una interfaz Function, que a su vez es un subtipo de Object. Considera el ejemplo anterior de myWith().
myWith(fish.name) {
capitalize()
}La interfaz Function tiene un método, invoke(), que se anula para llamar a la expresión lambda. Escrito a mano, se vería como el siguiente código.
// actually creates an object that looks like this
myWith(fish.name, object : Function1<String, Unit> {
override fun invoke(name: String) {
name.capitalize()
}
})Normalmente, esto no es un problema, ya que la creación de objetos y la llamada a funciones no generan mucha sobrecarga, es decir, tiempo de CPU y memoria. Pero si defines algo como myWith() que usas en todas partes, la sobrecarga podría acumularse.
Kotlin proporciona inline como una forma de controlar este caso para reducir la sobrecarga durante el tiempo de ejecución agregando un poco más de trabajo para el compilador. (Aprendiste un poco sobre inline en la lección anterior sobre tipos materializados). Marcar una función como inline significa que, cada vez que se llame a la función, el compilador transformará el código fuente para "insertar" la función. Es decir, el compilador cambiará el código para reemplazar la lambda por las instrucciones que contiene.
Si myWith() en el ejemplo anterior está marcado con inline:
inline myWith(fish.name) {
capitalize()
}se transforma en una llamada directa:
// with myWith() inline, this becomes
fish.name.capitalize()Vale la pena destacar que la inserción de funciones grandes aumenta el tamaño del código, por lo que es mejor usarla para funciones simples que se usan muchas veces, como myWith(). Las funciones de extensión de las bibliotecas que aprendiste antes están marcadas como inline, por lo que no tienes que preocuparte por la creación de objetos adicionales.
El método abstracto único solo significa una interfaz con un método. Son muy comunes cuando se usan APIs escritas en el lenguaje de programación Java, por lo que existe un acrónimo para ellas, SAM. Algunos ejemplos son Runnable, que tiene un solo método abstracto, run(), y Callable, que tiene un solo método abstracto, call().
En Kotlin, debes llamar a funciones que toman SAM como parámetros todo el tiempo. Prueba el siguiente ejemplo.
- Dentro de example, crea una clase Java,
JavaRun, y pega lo siguiente en el archivo.
package example;
public class JavaRun {
public static void runNow(Runnable runnable) {
runnable.run();
}
}Kotlin te permite crear una instancia de un objeto que implementa una interfaz anteponiendo el tipo con object:. Es útil para pasar parámetros a los SAM.
- De vuelta en
Fish.kt, crea una funciónrunExample()que cree unRunnableconobject:. El objeto debe implementarrun()imprimiendo"I'm a Runnable".
fun runExample() {
val runnable = object: Runnable {
override fun run() {
println("I'm a Runnable")
}
}
}- Llama a
JavaRun.runNow()con el objeto que creaste.
fun runExample() {
val runnable = object: Runnable {
override fun run() {
println("I'm a Runnable")
}
}
JavaRun.runNow(runnable)
}- Llama a
runExample()desdemain()y ejecuta el programa.
⇒ I'm a Runnable
Es mucho trabajo para imprimir algo, pero es un buen ejemplo de cómo funciona un SAM. Por supuesto, Kotlin proporciona una forma más simple de hacerlo: usa una lambda en lugar del objeto para que este código sea mucho más compacto.
- Quita el código existente en
runExample, cámbialo para que llame arunNow()con una lambda y ejecuta el programa.
fun runExample() {
JavaRun.runNow({
println("Passing a lambda as a Runnable")
})
}
⇒ Passing a lambda as a Runnable
- Puedes hacer que esto sea aún más conciso con la sintaxis de la última llamada al parámetro y deshacerte de los paréntesis.
fun runExample() {
JavaRun.runNow {
println("Last parameter is a lambda as a Runnable")
}
}⇒ Last parameter is a lambda as a Runnable
Esos son los conceptos básicos de un SAM, un método abstracto único. Puedes crear instancias, anular y llamar a un SAM con una línea de código, usando el patrón:Class.singleAbstractMethod { lambda_of_override }
En esta lección, se revisaron las expresiones lambda y se profundizó en las funciones de orden superior, que son partes clave de Kotlin. También aprendiste sobre las anotaciones y las pausas etiquetadas.
- Usa anotaciones para especificar elementos al compilador. Por ejemplo:
@file:JvmName("Foo") - Usa interrupciones etiquetadas para permitir que tu código salga de los bucles anidados. Por ejemplo:
if (i > 10) break@outerLoop // breaks to outerLoop label - Las lambdas pueden ser muy útiles cuando se combinan con funciones de orden superior.
- Las lambdas son objetos. Para evitar crear el objeto, puedes marcar la función con
inline, y el compilador colocará el contenido de la expresión lambda directamente en el código. - Usa
inlinecon cuidado, pero puede ayudar a reducir el uso de recursos de tu programa. - SAM, Single Abstract Method, es un patrón común que se simplifica con las lambdas. El patrón básico es:
Class.singleAbstractMethod { lamba_of_override } - La biblioteca estándar de Kotlin proporciona numerosas funciones útiles, incluidos varios SAM, por lo que es importante que sepas qué contiene.
Hay mucho más en Kotlin de lo que se abarcó en el curso, pero ahora tienes los conceptos básicos para comenzar a desarrollar tus propios programas en Kotlin. Esperamos que te entusiasme este lenguaje expresivo y que quieras crear más funciones escribiendo menos código (en especial, si vienes del lenguaje de programación Java). Practicar y aprender a medida que avanzas es la mejor manera de convertirte en un experto en Kotlin, así que sigue explorando y aprendiendo sobre Kotlin por tu cuenta.
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.
- Anotaciones
- Reflexión
- Pausas etiquetadas
- Lambdas y funciones de orden superior
- Funciones intercaladas
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.
Biblioteca estándar de Kotlin
La biblioteca estándar de Kotlin proporciona numerosas funciones útiles. Antes de escribir tu propia función o interfaz, siempre consulta la biblioteca estándar para ver si alguien te ahorró trabajo. Vuelve a consultar el sitio de vez en cuando, ya que se agregan funciones nuevas con frecuencia.
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
En Kotlin, SAM significa lo siguiente:
▢ Safe Argument Matching
▢ Método de acceso simple
▢ Método abstracto único
▢ Metodología de acceso estratégico
Pregunta 2
¿Cuál de las siguientes no es una función de extensión de la biblioteca estándar de Kotlin?
▢ elvis()
▢ apply()
▢ run()
▢ with()
Pregunta 3
¿Cuál de las siguientes afirmaciones sobre las lambdas en Kotlin no es verdadera?
▢ Las lambdas son funciones anónimas.
▢ Las expresiones lambda son objetos, a menos que se intercalen.
▢ Las lambdas consumen muchos recursos y no se deben usar.
▢ Las lambdas se pueden pasar a otras funciones.
Pregunta 4
Las etiquetas en Kotlin se indican con un identificador seguido de dos puntos:
▢ :
▢ ::
▢ @:
▢ @
¡Felicitaciones! Completaste el codelab de la Capacitación de Kotlin para programadores.
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".