Vamos a ver una colección de modismos que se utilizan con frecuencia.
Crear DTOs (POJOs/POCOs)
//sampleStart data class Persona(var nombre: String, var email: String) //sampleEnd fun main() { val persona = Persona("Pepe", "[email protected]") println("El nombre es ${persona.nombre}") persona.email= "[email protected]" println("El nuevo email es ${persona.email}") }
Con esa sola línea obtendríamos:
- getters (y setters en caso de utilizar
var
) de todas las propiedades equals()
hashCode()
toString()
copy()
component1()
,component2()
, …, para todas las propiedades (Ver Clases de Datos)
Valores por defecto de los parámetros de una función
//sampleStart fun ejemplo(numero: Int = 0, texto: String = "Vacio") = println("El numero es $numero y el texto es $texto") //sampleEnd fun main() { ejemplo() ejemplo(100,"hola") }
Filtrar una lista
fun main() { //sampleStart val lista = listOf(-2,-1,0,1,2,3) println("Lista sin filtrar: $lista") val numerosPositivos = lista.filter { x -> x > 0 } println("Lista filtrada: $numerosPositivos") //sampleEnd }
O una alternativa incluso más corta utilizando it
:
fun main() { val lista = listOf(-2,-1,0,1,2,3) println("Lista sin filtrar: $lista") //sampleStart val numerosPositivos= lista.filter { it > 0 } //sampleEnd println("Lista filtrada: $numerosPositivos") }
Interpolación de textos
fun main() { //sampleStart val nombre = "Javier" println("Su nombre es $nombre") // "Su nombre es Javier" //sampleEnd }
Controles de instancia
class Persona(val nombre: String) class Coche(val marca: String) val persona = Persona("Juan") val coche = Coche("SEAT") //sampleStart fun controlInstancia(parametro: Any) { when (parametro) { is Persona -> println("Es una persona cuyo nombre es ${parametro.nombre}") is Coche -> println("Es un coche de la marca ${parametro.marca}") else -> println("Desconocido") } } //sampleEnd fun main() { controlInstancia(persona) // "Es una persona" controlInstancia(coche) // "Es un coche" controlInstancia("hola") // "Desconocido" }
Recorrer un mapa/lista de pares
fun main() { //sampleStart val mapaPersonaCoche = mapOf("Juan" to "SEAT", "Pedro" to "Opel") for ((k, v) in mapaPersonaCoche) { println("$k -> $v") } //sampleEnd }
Alternativamente también podríamos hacerlo así:
fun main() { //sampleStart val mapaPersonaCoche = mapOf("Juan" to "SEAT", "Pedro" to "Opel") for (map in mapaPersonaCoche) { println("${map.key} -> ${map.value}") } //sampleEnd }
Intervalos
fun main() { //sampleStart val lista = mutableListOf<Int>() for (i in 1..10) lista.add(i) println(lista).also { lista.clear() } for (i in 1 until 20) lista.add(i) println(lista).also { lista.clear() } for (x in 2..10 step 2) lista.add(x) println(lista).also { lista.clear() } for (x in 10 downTo 1) lista.add(x) println(lista).also { lista.clear() } val x = 3 if (x in 1..10) print("Está en el intervalo") // Evita anidar 'if/for' colocando el intervalo en el 'if' //sampleEnd }
Como puedes ver, he utilizado .also
para que una vez que se haya mostrado la lista se limpie a posteriori. Observa también que aunque la lista es val
, es decir, no podrás hacer lista = otraList
pero si podrás modificar sus parámetros ya que es una MutableList
.
Listas
fun main() { //sampleStart val lista = listOf("a", "b", "c") val listaMutable = mutableListOf("a", "b", "c").apply { add("d") remove("b") } //sampleEnd println(lista) println(listaMutable) }
Con .apply
podemos utilizar varias funciones sobre el mismo objeto sin tener que escribirlo con cada función. Es muy útil cuando ya que además de ahorrarte código duplicado facilita su lectura.
Mapas
fun main() { //sampleStart val mapa = mapOf("a" to 1, "b" to 2, "c" to 3) //sampleEnd println(mapa) }
Como hemos dicho antes, en Kotlin puedes utilizar las variantes mutables de ciertas interfaces. Como ejemplo, las listas o los mapas tienen su variante mutable,
fun main() { //sampleStart val mapa = mutableMapOf("a" to 1, "b" to 2, "c" to 3) println(mapa["a"]) // "1" mapa["a"] = 10 // Utilizamos mutableMapOf para poder modificar el valor println(mapa["a"]) // "10" //sampleEnd }
Como puedes ver, aunque utilizamos val
podemos modificar los valores de cada clave. Si añadiésemos un nuevo valor también funcionaría:
fun main() { val mapa = mutableMapOf("a" to 1, "b" to 2, "c" to 3) //sampleStart mapa["e"] = 4 println(mapa["e"]) // "4" //sampleEnd }
Sin embargo si tratásemos de igualar mapa a otro mapa diferente, no podríamos compilar:
fun main() { //sampleStart val map = mapOf("a" to 1) val otroMap= mutableMapOf("b" to 2) map = otroMap //sampleEnd }
Atributo tardío (Lazy)
Cuando utilizas by lazy
lo que haces es que la variable no se inicie en el momento en el que la declaras. Se inicia en el momento en el que se usa.
//sampleStart val texto: String by lazy { "lazy property!" } //sampleEnd fun main() { println(texto) // Ahora se inicia la variable 'texto' }
Funciones de extensión
Esta es una de las características que más me gustan de Kotlin. Puedes añadir comportamientos o funcionalidades nuevas a cualquier clase. Por ejemplo vamos a añadir a String
una función que invierta la cadena de texto que le pasemos.
//sampleStart fun String.invertirTexto(): String { var textoInvertido = "" for (i in (this.length - 1 downTo 0)) { textoInvertido = textoInvertido + this.get(i) } return textoInvertido } //sampleEnd fun main() { val texto = "aniram al ne etatsíla" println(texto.invertirTexto()) // Mostraría 'Alístate en la marina' }
Un ejemplo de esto aplicado a Android sería crear una función para añadir una nueva funcionalidad a un ImageView
: Descargar imágenes de Internet con Picasso o Glide.
fun ImageView.loadUrl(url: String) { Picasso.with(context).load(url).into(this) } imageView.loadUrl(url) // Bye bye boilerplate!
He creado un repositorio en GitHub con una recopilación de estas funciones. Puedes sugerirme más mediante pull requests/issues o dejando un comentario en esta entrada para añadir más.
Crear un Singleton
//sampleStart object Recurso { val nombre = "Juan" } //sampleEnd fun main() { println(Recurso.nombre) // "Juan" }
También es posible crear un objeto anónimo:
fun main() { //sampleStart val recurso = object { val nombre = "Pepe" } //sampleEnd println(recurso.nombre) // "Pepe" }
Abreviación de “si no es nulo”
Imagina que tratas de obtener una lista de objetos de una API, y por algún motivo no puede obtener la lista, obteniendo una lista null
, para evitar un error simplemente utilizaríamos ?
de esta forma:
fun main() { //sampleStart val listaNull: List<String>? = null // Simulamos obtener una lista vacia println(listaNull?.size) // Compilaría y mostraría 'null' //sampleEnd }
Abreviación de “si no es nulo entonces”
También conocido como Elvis operator por su forma ?:
fun main() { //sampleStart val listaNull: List<String>? = null println(listaNull?.size ?: "Lista vacia") // "Lista vacia" //sampleEnd }
Obtener el primer ítem de una lista que puede estar vacía
fun main() { //sampleStart val listaNull: List<String> = listOf() println(listaNull.firstOrNull()) // "null" //sampleEnd }
Ejecutar si no es nulo
En el siguiente caso como la lista será nula, no imprimirá el texto, pero tampoco obtendremos una excepción. Esto es realmente útil en Android ya que muchas veces tenemos ciertos elementos que nunca sabemos si son nulos o no como el contexto en un fragmento. Con un símbolo de interrogación evitamos tener que colocar el clásico if (elemento != null) ...
fun main() { //sampleStart val listaNull: List<String>? = null listaNull?.let { println("La lista está vacía") } //sampleEnd }
Abreviación de “devolver cuando (when)”
//sampleStart fun obtenerColorPorID(color: Int): String { return when (color) { 0 -> "Rojo" 1 -> "Verde" 2 -> "Azul" else -> "Desconocido" } } //sampleEnd fun main() { val color = obtenerColorPorID(1) println(color) // "Verde" }
Expresión ‘try/catch’
//sampleStart fun mostrarCoche() { val coche = try { "SEAT" } catch (e: ArithmeticException) { throw IllegalStateException(e) } println(coche) } //sampleEnd fun main() { mostrarCoche() // Si no hay ningún error mostraría "SEAT" }
Expresión ‘if’
//sampleStart fun obtenerColorPorID(marca: Int) { val color = if (marca == 1) "Rojo" else "Verde" println(color) } //sampleEnd fun main() { obtenerColorPorID(1) // "Rojo" }
Uso de métodos de estilo constructor
//sampleStart fun rellenarDeAes(tamaño: Int): CharArray { return CharArray(tamaño).apply { fill('A') } } //sampleEnd fun main() { val charArrayAes = rellenarDeAes(4) for (i in 0 until charArrayAes.size) print("(${i + 1}, ${charArrayAes[i]}) ") }
Funciones de expresión única
La siguiente función:
//sampleStart fun nombre() = "Juan" //sampleEnd fun main() { println(nombre()) // "Juan" }
Es equivalente a:
//sampleStart fun nombre(): String { return "Juan" } //sampleEnd fun main() { println(nombre()) // "Juan" }
Estas funciones pueden ser combinadas con otros modismos, por ejemplo con un when
//sampleStart fun obtenerColorPorID(color: Int): String = when (color) { 0 -> "Rojo" 1 -> "Verde" 2 -> "Azul" else -> "Desconocido" } //sampleEnd fun main() { println(obtenerColorPorID(2)) // "Azul" }
Utilizar múltiples métodos de una instancia de objeto con ‘with’
Para evitar tener que escribir varias veces el mismo código y evitar duplicados, puedes utilizar todos los métodos de un objeto gracias a with
:
class Coches { fun seat() = "SEAT" fun opel() = "OPEL" fun bmw() = "BMW" } fun main() { val coches = Coches() //sampleStart with(coches) { val listaDeCoches = listOf(seat(), opel(), bmw()) println(listaDeCoches) } //sampleEnd }