Universidad del Quindío
Programa de Ingeniería de Sistemas y Computación
Título: Manual básico de Kotlin
Docente: Carlos Andrés Florez V.
Kotlin es un lenguaje de programación creado en 2010 por Jetbrains. Este lenguaje permite hacer que el desarrollo Android sea más fácil, rápido y mucho más agradable.
Kotlin es un lenguaje moderno, estáticamente tipado, compatible con Android que soluciona muchos problemas de Java, como las excepciones de puntero nulo o la excesiva verbosidad de código. Kotlin es un lenguaje inspirado en Swift, Scala, Groovy, C# y muchos otros lenguajes. Kotlin se esfuerza por no repetir los errores de otros lenguajes y aprovechar sus funciones más útiles. Cuando se trabaja con Kotlin, realmente se puede sentir que este es un lenguaje maduro y bien diseñado. Las ventajas principales de este moderno lenguaje de programación son:
A continuación se listan algunos ejemplos de las funcionalidades más importantes al momento de programar usando Kotlin.
Cada uno de los ejemplo que se prueban a continuación se puede probar en el compilador online: https://play.kotlinlang.org/
Kotlin es un lenguaje estáticamente tipado con inferencia de tipos, lo cual significa que el compilador puede deducir automáticamente el tipo de una variable a partir de su valor. Gracias a esto, no siempre es necesario declarar el tipo de forma explícita:
var algo1 = 1
var algo2 = true
var algo3 = "Prueba"
var algo4 = 1.2
var algo5 = 'A'
Sin embargo, si se desea tipar la variable también se puede usando la siguiente sintaxis:
var algo1: Int = 1
var algo2: Boolean = true
var algo3: String = "Prueba"
var algo4: Float = 1.2f
var algo5: Char = 'A'
Los tipos de datos de Kotlin son equivalentes a los existentes en Java, pero con pequeños cambios en los llamados. También existe el tipo de dato Any para definir que puede ser cualquier tipo.
Para la declaración de una variable sin la necesidad de inicializarla se hace lo siguiente:
var resultado: Int
También se puede usar la palabra reservada “val” en lugar de “var” para hacer que el valor almacenado no sea mutable, lo que es equivalente a las constantes en Java.
Para más información: https://kotlinlang.org/docs/basic-types.html
Los operadores y las expresiones aritmética relacionales y lógicas son equivalentes a las de Java por los cual se da por hecho el conocimiento de estas. Aunque cabe resaltar que la igualdad referencial (verificar si dos referencias apuntan al mismo objeto en memoria) se debe realizar usando el operador === (con tres signos igual), mientras que == verifica la igualdad estructural (equivalente a equals()).
Para más información sobre las igualdades: https://kotlinlang.org/docs/equality.html
La interpolación de cadenas en Kotlin permite insertar variables y expresiones directamente dentro de una cadena usando el signo $. Para variables simples se usa $variable, y para expresiones se usan llaves ${expresion}:
var cadena1: String = "Hola "
var cadena2 = "Mundo"
var anio = 2025
var saludo = "$cadena1 $cadena2, $anio"
// Interpolación con expresiones
var a = 5
var b = 3
var resultado = "La suma de $a + $b es ${a + b}"
Para la conversión de tipo, todos los tipos de datos básicos manejan la función toTipoDato, por ejemplo:
var numero: Int = 1
var nuevo = numero.toString()
Con el uso de la función toTipoDato() se pueden hacer el casting entre los diferentes tipos de datos básicos.
Una de las características más importantes de Kotlin es su sistema de seguridad contra nulos (null safety). En Kotlin, las variables por defecto no pueden contener valores nulos. Para permitir que una variable almacene un valor nulo, se debe declarar explícitamente usando el operador ?:
var nombre: String = "Carlos" // No puede ser null
var apellido: String? = null // Puede ser null
?.Permite acceder a propiedades o métodos de una variable que podría ser nula. Si la variable es nula, la expresión devuelve null en lugar de lanzar una excepción:
var nombre: String? = "Carlos"
println(nombre?.length) // 6
nombre = null
println(nombre?.length) // null (no lanza excepción)
?:Permite proporcionar un valor por defecto cuando una expresión es nula:
var nombre: String? = null
var longitud = nombre?.length ?: 0 // Si es null, asigna 0
var saludo = nombre ?: "Desconocido" // Si es null, asigna "Desconocido"
!!Convierte cualquier valor a un tipo no nulo y lanza una excepción si el valor es nulo. Debe usarse con precaución:
var nombre: String? = "Carlos"
println(nombre!!.length) // 6
nombre = null
// println(nombre!!.length) // Lanza NullPointerException
Para más información: https://kotlinlang.org/docs/null-safety.html
Los condicionales if, else y else if en Kotlin son iguales que en Java. La diferencia importante es que en Kotlin if puede usarse como expresión que devuelve un valor:
val deseo = "Casa"
var compra = if( deseo == "Casa" ){
"Carro"
}else{
"Casa"
}
// La variable compra almacena "Carro"
whenLas casos (en Java switch) cambian en sintaxis y palabras reservadas con respecto a Java. En Kotlin se debe usar la palabra when.
val mes = 7
when (mes) {
1 -> print("Enero")
2 -> print("Febrero")
3 -> print("Marzo")
4 -> print("Abril")
5 -> print("Mayo")
6 -> print("Junio")
7 -> print("Julio")
8 -> print("Agosto")
9 -> print("Septiembre")
10 -> print("Octubre")
11 -> print("Noviembre")
12 -> print("Diciembre")
else -> {
print("No corresponde a ningún mes del año")
}
}
when es una opción mucho más potente que su equivalente en Java con switch. La siguiente sintaxis es válida en Kotlin:
val mes = 7
when (mes) {
1, 2, 3 -> print("Primer trimestre del año")
4, 5, 6 -> print("segundo trimestre del año")
7, 8, 9 -> print("tercer trimestre del año")
10, 11, 12 -> print("cuarto trimestre del año")
}
Otra opción es usar in dentro de when:
val mes = 7
when (mes) {
in 1..6 -> print("Primer semestre")
in 7..12 -> print("segundo semestre")
!in 1..12 -> print("no es un mes válido")
}
También se puede hacer una comparación de tipos de datos usando is:
val valor: Any = 12
when (valor){
is Int -> print(valor + 1)
is String -> print("El texto es $valor")
is Boolean -> if (valor) print("es verdadero") else print("es falso")
}
Incluso se puede usar la expresión when como función y almacenar la respuesta de esta:
val mes = 7
val respuesta : String = when (mes) {
in 1..6 -> "Primer semestre"
in 7..12 -> "segundo semestre"
!in 1..12 -> "no es un mes válido"
else -> "error"
}
Para la declaración de funciones sin retorno se debe usar la palabra reservada fun seguida del identificador de la función y los paréntesis con los parámetros (si hay varios parámetros se separan con coma):
private fun mostrarInformacion( mensaje: String ){
println("Mostrar informacion $mensaje")
}
Para los casos en que es necesario el retorno de un valor se debe usar la siguiente sintaxis:
private fun dividir(numero1:Long, numero2:Long): Float{
return (numero1.toFloat() / numero2)
}
Las funciones en Kotlin permite el uso de argumentos (parámetros) por defecto y el la reducción de código eliminado las llaves, un ejemplo es los siguiente:
fun concatenarNombre(nombre: String="N/A", apellido: String="N/A"): String = "$nombre $apellido"
La anterior función permite ser llamada sin necesidad del envío de argumentos, o solo mandando uno.
concatenarNombre("Carlos")
El resultado de la función sería: "Carlos N/A"
O para concatenar el retorno de una función
println("El nombre es: ${concatenarNombre("Carlos")}")
Para la sobre escritura de una función se debe usar la palabra reservada override:
override fun toString(): String {
return super.toString()
}
Otras posibilidades con las funciones en Kotlin:
Los modificadores private, protected y public funcionan igual que en Java. La diferencia principal es que en Kotlin el modificador por defecto es public (no package-private como en Java), y se agrega internal que hace visible el elemento en todo el módulo al que pertenece.
El clásico ciclo for es representado en Kotlin de forma similar al foreach de Java.
for ( numero in 1..100 ){
println("el valor es: $numero")
}
Para recorridos inversos se puede usar la opción downTo.
for ( numero in 10 downTo 1 ){
println("el valor es: $numero")
}
Para los incrementos del índice en el valor que se desee se usa step.
for ( numero in 1..10 step 2 ){
println("el valor es: $numero")
}
Existe también la posibilidad de crear un rango en el cual se evita el último elemento. Para esto se debe usar until.
for ( numero in 1 until 4){
print("$numero, ")
}
El resultado del ciclo anterior sería: 1,2,3
Los ciclos while, do-while, break y continue son idénticos a Java.
Los arreglos en Kotlin al igual que en Java no pueden cambiar de tamaño. Su declaración e inicialización es la siguiente:
val dias: Array<String> = arrayOf("Lunes", "Martes", "Miercoles", "Jueves", "Viernes", "Sabado", "Domingo")
La primera posición del arreglo es la cero, y la última es la del tamaño del arreglo menos uno. Por lo cual para obtener la primero y última posición del anterior arreglo, se debe hacer lo siguiente:
dias[0]
dias.get(0)
dias[dias.size-1]
dias.get(dias.count()-1)
Como se puede ver se pueden usar los clásicos corchetes o el método get() y para obtener el tamaño del arreglo se puede usar size o count().
Para modificar el contenido de un elemento del arreglo se puede hacer los siguiente:
dias[0] = "Gran lunes"
dias.set(3, "Increíble viernes")
El recorrido de los arreglos es similar al que se realiza con los foreach, while y do-while en Java. El siguiente código permite ver el contenido del arreglo:
for ( dia in dias ){
println("día: $dia")
}
Y si se desea recorrer los índices:
for ( indice in dias.indices ){
println("indice: $indice")
}
Pero si se necesita tanto el elemento como el índice:
for ( (indice, dia) in dias.withIndex() ){
println("indice: $indice y día: $dia")
}
Por último para crear una arreglo vacío se usa lo siguiente:
val arreglo = arrayOfNulls<Int>(100)
Las listas en Kotlin se pueden clasificar en no mutables (solo lectura) y mutables. Para las no mutables se puede usar la siguiente sintaxis en sus operaciones más importantes:
val soloLectura: List<Int> = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 0)
soloLectura.size //Muestra el tamaño de la lista
soloLectura.get(3) //Devuelve el valor de la posición 3
soloLectura.first() //Devuelve el primer valor
soloLectura.last() //Devuelve el último valor
println(soloLectura) //[1, 2, 3, 4, 5, 6, 7, 8, 9, 0]
En cuanto al recorrido, se usa la misma sintaxis que con los arreglos. La ventaja de usar las listas no mutables es que se maneja de forma más eficiente la memoria.
Las listas mutables tienen las mismas funcionalidades que las de solo lectura, y además de eso la capacidad de cambiar los valores de los elementos, eliminarlos y agregar elementos nuevos.
val listaMutable: MutableList<Int> = mutableListOf(1, 2, 3, 4, 5, 6, 7, 8, 9)
O sin valores
val meses:MutableList<String> = mutableListOf()
Para agregar un elemento se puede agregándolo usando add (lo pone al final de la lista)
meses.add("Enero")
O indicando la posición donde lo desea almacenar (Similar a las listas en Java).
meses.add(0, "Meses")
Las opciones básicas para eliminar elementos desde la lista mutable se puede usar una posición o un elemento.
meses.removeAt(0)
meses.remove("Febrero")
La primera opción elimina la posición indicada y devuelve como resultado el elemento eliminado. La segunda opción elimina al elemento que se especifique, de encontrarlo en la lista lo elimina y retorna true, de lo contrario devuelve false.
Otras opciones interesantes con las listas mutables son:
meses.none() //Devuelve un true si la lista está vacía
meses.firstOrNull() //El primer elemento, y si no hay elemento devuelve un null.
meses.elementAtOrNull(2) //El elemento del índice 2, si no hay, devolverá un null
meses.lastOrNull() //Último valor de la lista o null
Para el recorrido de las listas se puede usar la misma sintaxis que con los arreglos, entre estas opciones está el forEach para Kotlin.
meses.forEach { mes ->
println("Mes: $mes")
}
Como se ve, por medio del nombre asignado a la variable lambda (mes en este caso) se puede acceder a cada uno de los elementos de la lista (o arreglo). Si no se asigna un nombre, se puede usar la referencia implícita it.
Además de las anteriores hay una importante variedad de colecciones que permiten bajar la complejidad de los algoritmos.
Para más información: https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.collections/-list/
La sintaxis para programar una clase con Kotlin es igualmente sencilla.
class Persona{
}
Para el método constructor se debe usar la palabra reservada constructor en la misma línea de la declaración de la clase. Cuando el constructor esta al iniciar la clase, se llama constructor primario.
class Persona constructor(nombre: String){
}
Si el constructor no tiene ninguna anotación o modificadores de visibilidad, la palabra clave constructor se puede omitir:
class Persona (nombre: String){
}
También se pueden tener constructores secundarios que deben delegar al primario usando this, y bloques init que se ejecutan al crear la instancia:
class Persona(val nombre: String) {
init {
// Se ejecuta al crear la instancia
}
constructor(nombre : String, edad: Int) : this(nombre) {
// Constructor secundario, delega al primario
}
}
Por defecto en Kotlin todas las clases son final, por lo cual si se quiere dejar que una clase se pueda heredar, esta debe estar acompañada del modificador open.
open class Persona(val nombre: String) {
}
class Empleado(nombre:String): Persona(nombre) {
}
Al igual que Java no se permite herencia múltiple.
dataLas clases data son clases especiales que están destinadas a almacenar datos. Al declarar una clase como data, el compilador genera automáticamente varios métodos útiles para esa clase, como equals(), hashCode(), toString(), y copy(), basándose en las propiedades definidas en el constructor primario.
data class Usuario(val nombre: String, val edad: Int)
Es importante destacar que para que una clase pueda ser declarada como data, debe cumplir con ciertos requisitos:
val o var.open, sealed o inner.sealedUna clase sealed es una clase que restringe la herencia a un conjunto específico de subclases. Esto significa que todas las subclases de una clase sealed deben ser declaradas en el mismo archivo fuente. Las clases sealed son útiles cuando se desea representar una jerarquía de tipos con un número limitado y conocido de subtipos.
sealed class Resultado{
data class Exito(val datos: String) : Resultado()
data class Error(val mensaje: String) : Resultado()
}
Para más información: https://kotlinlang.org/docs/classes.html
Las interfaces en Kotlin funcionan igual que en Java (incluyendo métodos con implementación por defecto y herencia múltiple de interfaces). La única diferencia es la sintaxis para implementarlas, que usa : en lugar de implements:
interface OperacionesMatematicas{
fun sumar(n1: Int, n2: Int): Int
fun multiplicar(n1: Int, n2: Int): Int = n1 * n2 // Implementación por defecto
}
class Calculadora : OperacionesMatematicas {
override fun sumar(n1: Int, n2: Int): Int = n1 + n2
}
Para más información: https://kotlinlang.org/docs/interfaces.html
Las clases enumeradas permiten definir un conjunto fijo de constantes con nombre. Son útiles para representar un grupo limitado de valores posibles:
enum class DiaSemana {
LUNES, MARTES, MIERCOLES, JUEVES, VIERNES, SABADO, DOMINGO
}
val dia = DiaSemana.LUNES
Las enumeraciones también pueden tener propiedades y métodos:
enum class Color(val hex: String) {
ROJO("#FF0000"),
VERDE("#00FF00"),
AZUL("#0000FF");
fun mostrar() = "Color: $name, Hex: $hex"
}
val color = Color.ROJO
println(color.mostrar()) // Color: ROJO, Hex: #FF0000
Las enumeraciones se pueden usar con la expresión when:
fun describir(dia: DiaSemana): String = when(dia) {
DiaSemana.SABADO, DiaSemana.DOMINGO -> "Fin de semana"
else -> "Día laboral"
}
Para más información: https://kotlinlang.org/docs/enum-classes.html
La desestructuración permite descomponer un objeto en varias variables de forma simultánea. Esto funciona con clases data, pares, mapas y otros tipos:
data class Persona(val nombre: String, val edad: Int)
val persona = Persona("Carlos", 30)
val (nombre, edad) = persona
println("Nombre: $nombre, Edad: $edad")
También se puede usar en ciclos para recorrer mapas:
val capitales = mapOf("Colombia" to "Bogotá", "Perú" to "Lima")
for ((pais, capital) in capitales) {
println("$pais -> $capital")
}
Y con pares y tripletas:
val par = Pair("Kotlin", 2010)
val (lenguaje, anio) = par
val triple = Triple("Kotlin", "JetBrains", 2010)
val (nombre2, empresa, anio2) = triple
Para más información: https://kotlinlang.org/docs/destructuring-declarations.html
Kotlin permite agregar nuevas funciones a clases existentes sin modificarlas ni heredar de ellas. Se definen usando la sintaxis NombreClase.nombreFuncion():
fun String.esPalindromo(): Boolean {
val limpio = this.lowercase().replace(" ", "")
return limpio == limpio.reversed()
}
println("anilina".esPalindromo()) // true
println("kotlin".esPalindromo()) // false
fun Int.esPar(): Boolean = this % 2 == 0
println(4.esPar()) // true
println(7.esPar()) // false
Las funciones de extensión son ampliamente utilizadas en el ecosistema Android y en las bibliotecas de Kotlin.
Para más información: https://kotlinlang.org/docs/extensions.html
El try-catch-finally y throw funcionan igual que en Java. Las dos diferencias importantes son:
try puede usarse como expresión que devuelve un valor:val numero: Int = try {
"abc".toInt()
} catch (e: NumberFormatException) {
0 // Valor por defecto si falla la conversión
}
Para más información: https://kotlinlang.org/docs/exceptions.html
Las corutinas son una característica de Kotlin que permite escribir código asíncrono de manera más sencilla y legible. Las corutinas son ligeras y pueden ser suspendidas y reanudadas sin bloquear el hilo en el que se ejecutan, lo que las hace ideales para tareas que requieren espera, como operaciones de red o acceso a bases de datos.
Para usar corutinas en Kotlin, se utiliza la biblioteca kotlinx.coroutines. A continuación se presentan los conceptos fundamentales:
runBlocking: Crea una corutina que bloquea el hilo actual hasta que se complete. Se usa principalmente en funciones main() y en pruebas.launch: Inicia una nueva corutina que no devuelve un resultado. Devuelve un Job que se puede usar para controlar la corutina.async: Inicia una corutina que devuelve un resultado a través de un Deferred. Se usa await() para obtener el resultado.import kotlinx.coroutines.*
fun main() = runBlocking {
launch {
delay(1000L)
println("Mundo!")
}
println("Hola,")
}
// Imprime: Hola, (espera 1 segundo) Mundo!
Una función suspend es una función que puede ser pausada y reanudada más tarde. Solo puede ser llamada desde otra función suspendida o desde una corutina:
suspend fun obtenerDatos(): String {
delay(2000L) // Simula una operación larga sin bloquear el hilo
return "Datos obtenidos"
}
fun main() = runBlocking {
val resultado = obtenerDatos()
println(resultado)
}
Para más información: https://kotlinlang.org/docs/coroutines-overview.html
Para el desarrollo de aplicaciones usando Kotlin se usarán las mismas buenas prácticas que se tienen con Java.
En este apartado se presentan ejercicios diseñados para practicar las características propias de Kotlin vistas en esta guía. Cada ejercicio debe resolverse usando el compilador online https://play.kotlinlang.org/ o en un proyecto de consola en IntelliJ IDEA.
Implemente lo siguiente:
Una data class llamada Producto con las propiedades: nombre (String), precio (Double) y cantidad (Int).
Una función de extensión sobre Producto llamada valorTotal() que retorne el precio multiplicado por la cantidad.
List<Producto> llamada resumen() que retorne un String con el siguiente formato (usando interpolación de cadenas):
```
Inventario (n productos):
aplicarDescuento que reciba un Producto y un porcentaje de descuento con valor por defecto de 10%, y retorne una copia del producto con el precio modificado (usar copy()).Probar todo desde la función main creando al menos 5 productos, aplicando descuento a algunos y mostrando el resumen.
Modele un sistema de gestión de tareas:
Un enum class llamado Prioridad con los valores BAJA, MEDIA, ALTA y CRITICA. Cada valor debe tener una propiedad nivel de tipo Int (1 a 4 respectivamente).
sealed class llamada EstadoTarea con las siguientes subclases:
Pendiente (sin propiedades adicionales)EnProgreso(val porcentaje: Int)Completada(val fechaFinalizacion: String)Cancelada(val motivo: String)Una data class llamada Tarea con: titulo (String), descripcion (String?), prioridad (Prioridad) y estado (EstadoTarea).
Una función mostrarEstado que reciba una Tarea y, usando when sobre el estado, imprima un mensaje descriptivo diferente para cada caso (aprovechando el smart cast de las sealed classes).
main, crear una MutableList<Tarea> con al menos 6 tareas en diferentes estados y prioridades. Implementar las siguientes operaciones sobre la lista:
mostrarEstado y un forEach.ALTA o CRITICA que no estén canceladas.groupBy).filter, maxByOrNull).Modele un sistema hospitalario usando las características de Kotlin vistas en la guía (data class, enum class, null safety, funciones de extensión, herencia).
Un hospital tiene médicos y pacientes. Ambos comparten nombre, identificación y género (modelado como enum class). Los médicos además tienen especialidad (modelada como enum class), salario y año de ingreso. Los pacientes tienen teléfono y dirección (modelar como data class con calle, ciudad y código postal). El correo electrónico de cualquier persona puede ser nulo.
El hospital almacena listas de médicos y pacientes. El software debe permitir:
List<Medico> que filtre por especialidad.List<Paciente> que filtre por ciudad.No se requiere interfaz gráfica de usuario.