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
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:
- Seguro contra nulos.
- Ahorro de código.
- Características de programación funcional (higher-order functions, function types y lambdas).
- Fácil de usar.
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/
Variables
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
Operadores y expresiones
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
Concatenación e interpolación de cadenas
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}"
Conversión de tipo (casting)
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.
Null Safety
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
Operador de llamada segura ?.
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)
Operador Elvis ?:
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"
Operador de aserción no nula !!
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
Condiciones con if
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"
Condiciones con when
Las 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"
}
Funciones
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:
- Recibir como parámetro una función.
- Retornar una función.
- Agregar una función dentro de otra.
- Funciones anónimas.
- Mucho más: https://kotlinlang.org/docs/functions.html
Modificadores de visibilidad
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.
Ciclos
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.
Arreglos
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)
Listas
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:
Declaración e inicialización
val soloLectura: List<Int> = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 0)
Operaciones
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.
Declaración con valores
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/
Clases
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.
Clases data
Las 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:
- Debe tener al menos un parámetro en el constructor primario.
- Todos los parámetros del constructor primario deben ser marcados como
valovar. - No puede ser una clase abstracta,
open,sealedoinner.
Clases sealed
Una 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
Interfaces
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
Enumeraciones (Enum Classes)
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
Destructuring Declarations
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
Funciones de Extensión
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
Manejo de Excepciones
El try-catch-finally y throw funcionan igual que en Java. Las dos diferencias importantes son:
- Kotlin no tiene checked exceptions: no obliga a declarar ni capturar excepciones en tiempo de compilación.
trypuede 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
Coroutines
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:
Builders de corutinas
runBlocking: Crea una corutina que bloquea el hilo actual hasta que se complete. Se usa principalmente en funcionesmain()y en pruebas.launch: Inicia una nueva corutina que no devuelve un resultado. Devuelve unJobque se puede usar para controlar la corutina.async: Inicia una corutina que devuelve un resultado a través de unDeferred. Se usaawait()para obtener el resultado.
import kotlinx.coroutines.*
fun main() = runBlocking {
launch {
delay(1000L)
println("Mundo!")
}
println("Hola,")
}
// Imprime: Hola, (espera 1 segundo) Mundo!
Funciones suspendidas
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
Estándares de codificación y documentación
Para el desarrollo de aplicaciones usando Kotlin se usarán las mismas buenas prácticas que se tienen con Java.
Actividad práctica
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.
Ejercicio 1: Funciones, extensiones y data classes
Implemente lo siguiente:
-
Una
data classllamadaProductocon las propiedades:nombre(String),precio(Double) ycantidad(Int). -
Una función de extensión sobre
ProductollamadavalorTotal()que retorne el precio multiplicado por la cantidad. - Una función de extensión sobre
List<Producto>llamadaresumen()que retorne unStringcon el siguiente formato (usando interpolación de cadenas): ``` Inventario (n productos):- Producto1: $precio x cantidad = $total
- Producto2: $precio x cantidad = $total Total inventario: $totalGeneral ```
- Una función llamada
aplicarDescuentoque reciba unProductoy un porcentaje de descuento con valor por defecto de 10%, y retorne una copia del producto con el precio modificado (usarcopy()).
Probar todo desde la función main creando al menos 5 productos, aplicando descuento a algunos y mostrando el resumen.
Ejercicio 2: Enums, sealed classes y listas
Modele un sistema de gestión de tareas:
-
Un
enum classllamadoPrioridadcon los valoresBAJA,MEDIA,ALTAyCRITICA. Cada valor debe tener una propiedadnivelde tipoInt(1 a 4 respectivamente). - Una
sealed classllamadaEstadoTareacon las siguientes subclases:Pendiente(sin propiedades adicionales)EnProgreso(val porcentaje: Int)Completada(val fechaFinalizacion: String)Cancelada(val motivo: String)
-
Una
data classllamadaTareacon:titulo(String),descripcion(String?),prioridad(Prioridad) yestado(EstadoTarea). -
Una función
mostrarEstadoque reciba unaTareay, usandowhensobre el estado, imprima un mensaje descriptivo diferente para cada caso (aprovechando el smart cast de las sealed classes). - En la función
main, crear unaMutableList<Tarea>con al menos 6 tareas en diferentes estados y prioridades. Implementar las siguientes operaciones sobre la lista:- Mostrar el estado de todas las tareas usando
mostrarEstadoy unforEach. - Filtrar y mostrar solo las tareas con prioridad
ALTAoCRITICAque no estén canceladas. - Contar cuántas tareas hay en cada estado (usar
groupBy). - Obtener la tarea con mayor prioridad que esté pendiente (usar
filter,maxByOrNull). - Usar destructuring para recorrer las tareas e imprimir solo el título y la prioridad.
- Mostrar el estado de todas las tareas usando
Ejercicio 3: Sistema de gestión hospitalaria
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:
- Agregar y eliminar médicos y pacientes.
- Calcular el total de salarios por especialidad.
- Obtener el médico con más antigüedad.
- Crear una función de extensión sobre
List<Medico>que filtre por especialidad. - Crear una función de extensión sobre
List<Paciente>que filtre por ciudad.
No se requiere interfaz gráfica de usuario.