Tabla de Contenidos
Anexo — Fundamentos del lenguaje Kotlin
1. Introducción
Kotlin es un lenguaje de programación moderno desarrollado por JetBrains. Puede utilizarse para desarrollar distintos tipos de aplicaciones y tiene una especial importancia en el desarrollo de aplicaciones Android.
Kotlin es interoperable con Java, lo que permite utilizar desde Kotlin gran parte del código y de las bibliotecas desarrolladas originalmente para Java.
En este anexo estudiaremos los fundamentos del lenguaje necesarios antes de comenzar el desarrollo de aplicaciones Android.
Los contenidos se dividen en dos grandes bloques:
* Fundamentos del lenguaje Kotlin. * Programación Orientada a Objetos con Kotlin.
Objetivo
El objetivo de este tema no es conocer todas las posibilidades del lenguaje Kotlin, sino disponer de una base suficiente para comenzar posteriormente el desarrollo de aplicaciones Android sin tener que aprender simultáneamente los fundamentos del lenguaje.
2. Nuestro primer programa
Un programa Kotlin sencillo puede escribirse de la siguiente forma:
fun main() { println("Hola mundo") }
El resultado será:
Hola mundo
2.1. La función main()
La función:
fun main() { }
es el punto de entrada de una aplicación Kotlin de consola.
Las instrucciones contenidas entre `{` y `}` se ejecutarán cuando comience el programa.
Por ejemplo:
fun main() { println("Comenzando programa...") println("Aprendiendo Kotlin") println("Fin del programa") }
2.2. Punto y coma
En Kotlin no es necesario escribir `;` al final de cada instrucción.
val nombre = "Ana" println(nombre)
Aunque Kotlin permite utilizar punto y coma en determinadas situaciones, normalmente no se utiliza.
3. Comentarios
Los comentarios permiten introducir explicaciones en el código.
El compilador los ignora.
3.1. Comentarios de una línea
// Esto es un comentario val edad = 20
También podemos escribirlo después de una instrucción:
val edad = 20 // Edad del alumno
3.2. Comentarios de varias líneas
/*
Este comentario
ocupa varias líneas
*/
Utiliza comentarios para explicar decisiones o partes del código que no resulten evidentes.
No es necesario explicar mediante comentarios cosas que el propio código deja claras.
4. Mostrar información por pantalla
Kotlin proporciona las funciones `print()` y `println()`.
4.1. println()
Muestra un valor y después realiza un salto de línea.
println("Uno") println("Dos") println("Tres")
Resultado:
Uno Dos Tres
4.2. print()
Muestra un valor sin realizar un salto de línea.
print("Hola ") print("mundo")
Resultado:
Hola mundo
5. Variables
Una variable es un espacio que utilizamos para almacenar información.
En Kotlin existen dos formas principales de declarar variables:
* `val` * `var`
5.1. val
Una variable declarada mediante `val` no puede ser reasignada.
val nombre = "Ana" println(nombre)
No podríamos hacer posteriormente:
val nombre = "Ana" nombre = "Luis" // ERROR
5.2. var
Una variable declarada mediante `var` sí puede ser reasignada.
var edad = 20 edad = 21 edad = 22 println(edad)
Resultado:
22
5.3. ¿val o var?
La regla general que seguiremos será:
Utiliza `val` siempre que sea posible.
Utiliza `var` solamente cuando necesites reasignar la variable.
Por ejemplo:
val nombre = "Ana" var puntuacion = 0 puntuacion = puntuacion + 10
El nombre no cambia, por lo que utilizamos `val`.
La puntuación puede cambiar durante el programa, por lo que utilizamos `var`.
5.4. Identificadores
Debemos utilizar nombres descriptivos para nuestras variables.
Evitar:
val x = "Carlos" val n = 25
Preferir:
val nombreAlumno = "Carlos" val numeroAlumnos = 25
En Kotlin utilizamos normalmente la notación camelCase:
val nombreCompleto = "Ana García" val numeroDeAlumnos = 25 val precioFinal = 19.95
Los nombres de variables y funciones suelen comenzar con minúscula.
6. Tipos de datos
Cada variable tiene asociado un tipo de dato.
El tipo determina:
* Qué información puede almacenar. * Qué operaciones podemos realizar con ella. * Qué cantidad de memoria puede necesitar su representación.
Algunos de los tipos básicos más importantes de Kotlin son:
| Tipo | Utilidad |
|---|---|
| `Byte` | Enteros pequeños |
| `Short` | Enteros |
| `Int` | Enteros |
| `Long` | Enteros grandes |
| `Float` | Números decimales |
| `Double` | Números decimales de mayor precisión |
| `Boolean` | Valores verdadero/falso |
| `Char` | Un carácter |
| `String` | Cadenas de texto |
7. Tipos enteros
Kotlin proporciona cuatro tipos principales para representar números enteros:
| Tipo | Tamaño | Valor mínimo | Valor máximo |
|---|---|---|---|
| `Byte` | 8 bits | -128 | 127 |
| `Short` | 16 bits | -32.768 | 32.767 |
| `Int` | 32 bits | -2.147.483.648 | 2.147.483.647 |
| `Long` | 64 bits | -9.223.372.036.854.775.808 | 9.223.372.036.854.775.807 |
7.1. Int
Es el tipo entero que utilizaremos habitualmente.
val edad: Int = 20 val alumnos: Int = 28 val temperatura: Int = -4
7.2. Long
Se utiliza para números enteros que pueden superar el rango de `Int`.
val poblacion: Long = 8_000_000_000
También podemos utilizar el sufijo `L`:
val numero = 8_000_000_000L
7.3. Byte y Short
Podemos declarar:
val edad: Byte = 20 val temperatura: Short = -500
Sin embargo, en programas habituales no solemos elegir `Byte` o `Short` simplemente para ahorrar memoria.
Normalmente utilizaremos:
* `Int` para números enteros. * `Long` cuando `Int` no sea suficiente. * `Byte` o `Short` cuando una API o formato de datos concreto lo requiera.
7.4. Separador de miles
Kotlin permite utilizar `_` para hacer números largos más legibles:
val poblacion = 48_000_000 val presupuesto = 1_250_000
El carácter `_` no afecta al valor.
8. Tipos decimales
Kotlin proporciona dos tipos principales:
| Tipo | Tamaño | Precisión aproximada |
|---|---|---|
| `Float` | 32 bits | 6-7 dígitos decimales significativos |
| `Double` | 64 bits | 15-16 dígitos decimales significativos |
8.1. Double
Es el tipo que Kotlin utiliza por defecto para literales decimales.
val altura = 1.82 val precio = 19.95
Kotlin deduce que ambos son `Double`.
También podemos indicarlo explícitamente:
val altura: Double = 1.82
8.2. Float
Para crear un literal `Float` utilizamos normalmente `f`:
val temperatura: Float = 23.5f
Esto no sería válido:
val temperatura: Float = 23.5
porque `23.5` es interpretado inicialmente como `Double`.
Como regla general:
* Utiliza `Int` para números enteros. * Utiliza `Double` para números decimales.
Utilizaremos otros tipos cuando exista algún motivo concreto para hacerlo.
9. Boolean
El tipo `Boolean` representa valores lógicos.
Solamente puede tener dos valores:
true false
Por ejemplo:
val mayorDeEdad = true val tieneCarnet = false
Los booleanos serán fundamentales cuando trabajemos con condiciones:
val edad = 20 val mayorDeEdad = edad >= 18 println(mayorDeEdad)
Resultado:
true
10. Char
`Char` representa un carácter.
Se escribe entre comillas simples:
val letra: Char = 'A' val simbolo: Char = '?' val numero: Char = '7'
No debemos confundir:
'A'
con:
"A"
El primero es un `Char`.
El segundo es un `String`.
11. String
Un `String` representa una cadena de caracteres.
val nombre = "Carlos" val ciudad = "León" val mensaje = "Aprendiendo Kotlin"
11.1. Longitud
Podemos obtener la longitud mediante `length`:
val lenguaje = "Kotlin" println(lenguaje.length)
Resultado:
6
11.2. Acceder a caracteres
Los caracteres están numerados comenzando por `0`.
val lenguaje = "Kotlin" println(lenguaje[0]) println(lenguaje[1])
Resultado:
K o
11.3. Concatenar cadenas
Podemos concatenar mediante `+`:
val nombre = "Ana" val apellido = "García" val nombreCompleto = nombre + " " + apellido println(nombreCompleto)
Sin embargo, Kotlin proporciona una opción más cómoda: la interpolación de cadenas.
12. Interpolación de cadenas
Podemos introducir el valor de una variable dentro de una cadena utilizando `$`.
val nombre = "Ana" val edad = 20 println("Me llamo $nombre y tengo $edad años")
Para introducir una expresión utilizamos `${}`:
val edad = 20 println("El año que viene tendré ${edad + 1} años")
Otro ejemplo:
val precio = 20.0 val cantidad = 3 println("Total: ${precio * cantidad} euros")
La interpolación de cadenas es muy habitual en Kotlin y resulta normalmente más legible que concatenar múltiples cadenas con `+`.
13. Inferencia de tipos
Kotlin es un lenguaje estáticamente tipado.
Eso significa que las variables tienen un tipo conocido durante la compilación.
Sin embargo, no siempre necesitamos escribirlo.
Por ejemplo:
val edad = 20 val nombre = "Ana" val altura = 1.75 val activo = true
Kotlin deduce automáticamente:
edad -> Int nombre -> String altura -> Double activo -> Boolean
Esto se denomina inferencia de tipos.
Podríamos escribir exactamente lo mismo indicando los tipos:
val edad: Int = 20 val nombre: String = "Ana" val altura: Double = 1.75 val activo: Boolean = true
Normalmente utilizaremos inferencia cuando el tipo sea evidente.
14. Conversión de tipos
Kotlin evita realizar automáticamente muchas conversiones entre tipos numéricos.
Por ejemplo:
val numero: Int = 10 // ERROR // val decimal: Double = numero
Debemos convertirlo explícitamente:
val numero: Int = 10 val decimal: Double = numero.toDouble()
Existen funciones como:
toByte() toShort() toInt() toLong() toFloat() toDouble() toString()
Ejemplo:
val numero = 25 val comoDouble = numero.toDouble() val comoLong = numero.toLong() val comoString = numero.toString()
14.1. Conversión de decimal a entero
val precio = 19.95 val entero = precio.toInt() println(entero)
Resultado:
19
La parte decimal se descarta.
Una conversión de tipo puede producir pérdida de información.
Por ejemplo, convertir `19.95` a `Int` produce `19`.
15. Entrada de datos por teclado
Aunque posteriormente trabajaremos principalmente con interfaces Android, para practicar Kotlin resulta útil crear pequeños programas de consola.
Podemos leer texto mediante:
val nombre = readln()
Ejemplo:
fun main() { print("Introduce tu nombre: ") val nombre = readln() ``` println("Hola $nombre") ``` }
15.1. Leer números
`readln()` devuelve un `String`.
Por tanto, si queremos leer un entero debemos convertirlo:
print("Introduce tu edad: ") val edad = readln().toInt() println("Tienes $edad años")
Para un `Double`:
print("Introduce el precio: ") val precio = readln().toDouble()
Si intentamos convertir a número un texto que no representa un número válido se producirá un error.
Más adelante veremos formas de realizar conversiones de manera segura.
16. Operadores aritméticos
| Operador | Operación |
|---|---|
| `+` | Suma |
| `-` | Resta |
| `*` | Multiplicación |
| `/` | División |
| `%` | Resto de una división |
Ejemplo:
val a = 10 val b = 3 println(a + b) println(a - b) println(a * b) println(a / b) println(a % b)
16.1. División entera
Si dividimos dos enteros:
val resultado = 10 / 3 println(resultado)
obtenemos:
3
No `3.3333`.
Para obtener decimales uno de los operandos debe ser decimal:
val resultado = 10.0 / 3 println(resultado)
16.2. Operador módulo %
El operador `%` devuelve el resto.
println(10 % 3)
Resultado:
1
Podemos utilizarlo, por ejemplo, para determinar si un número es par:
val numero = 8 if (numero % 2 == 0) { println("El número es par") }
17. Operadores de asignación
var numero = 10 numero += 5 numero -= 2 numero *= 3 numero /= 2
Por ejemplo:
numero += 5
es una forma abreviada de expresar:
numero = numero + 5
18. Incremento y decremento
Podemos incrementar una variable:
var contador = 0 contador++
o decrementarla:
contador--
19. Operadores de comparación
Las comparaciones producen valores `Boolean`.
| Operador | Significado |
|---|---|
| `==` | Igual |
| `!=` | Distinto |
| `>` | Mayor |
| `<` | Menor |
| `>=` | Mayor o igual |
| `⇐` | Menor o igual |
Ejemplo:
val edad = 20 println(edad >= 18) println(edad == 20) println(edad != 30)
19.1. Diferencia entre = y ==
`=` realiza una asignación:
var edad = 20 edad = 21
`==` compara valores:
if (edad == 21) { println("Tiene 21 años") }
20. Operadores lógicos
| Operador | Significado | ||
|---|---|---|---|
| `&&` | AND | ||
| ` | ` | OR | |
| `!` | NOT | ||
20.1. AND &&
Las dos condiciones deben cumplirse:
val edad = 20 val tieneCarnet = true if (edad >= 18 && tieneCarnet) { println("Puede conducir") }
20.2. OR ||
Debe cumplirse al menos una:
val esSabado = true val esDomingo = false if (esSabado || esDomingo) { println("Es fin de semana") }
20.3. NOT !
Invierte un booleano:
val activo = true println(!activo)
Resultado:
false
21. Estructura if
Permite ejecutar código cuando se cumple una condición.
if (condicion) { // instrucciones }
Ejemplo:
val edad = 20 if (edad >= 18) { println("Mayor de edad") }
21.1. if...else
if (edad >= 18) { println("Mayor de edad") } else { println("Menor de edad") }
21.2. else if
val nota = 7 if (nota >= 9) { println("Sobresaliente") } else if (nota >= 7) { println("Notable") } else if (nota >= 5) { println("Aprobado") } else { println("Suspenso") }
21.3. if como expresión
En Kotlin `if` puede devolver un valor.
val edad = 20 val resultado = if (edad >= 18) { "Mayor de edad" } else { "Menor de edad" }
También:
val resultado = if (edad >= 18) "Mayor de edad" else "Menor de edad"
Kotlin no necesita el operador ternario presente en otros lenguajes.
22. when
`when` permite seleccionar una alternativa entre varias posibilidades.
val opcion = 2 when (opcion) { 1 -> println("Alta") 2 -> println("Consulta") 3 -> println("Modificación") 4 -> println("Baja") else -> println("Opción incorrecta") }
22.1. Agrupar valores
val dia = 6 when (dia) { 1, 2, 3, 4, 5 -> println("Laborable") 6, 7 -> println("Fin de semana") else -> println("Valor incorrecto") }
22.2. when como expresión
val nota = 8 val calificacion = when (nota) { 9, 10 -> "Sobresaliente" 7, 8 -> "Notable" 5, 6 -> "Aprobado" else -> "Suspenso" } println(calificacion)
22.3. when con condiciones
val edad = 35 when { edad < 18 -> println("Menor de edad") edad < 65 -> println("Adulto") else -> println("Mayor de 65 años") }
23. while
Un `while` repite instrucciones mientras se cumpla una condición.
var contador = 1 while (contador <= 5) { println(contador) contador++ }
Resultado:
1 2 3 4 5
23.1. Bucles infinitos
Debemos asegurarnos de que la condición pueda dejar de cumplirse.
Este código no terminará:
var contador = 1 while (contador <= 5) { println(contador) }
porque `contador` nunca cambia.
24. do...while
`do…while` ejecuta primero y comprueba después.
var numero = 1 do { println(numero) numero++ } while (numero <= 5)
Por tanto, el cuerpo se ejecutará al menos una vez.
25. break y continue
25.1. break
Finaliza el bucle:
for (numero in 1..10) { if (numero == 5) { break } ``` println(numero) ``` }
Resultado:
1 2 3 4
25.2. continue
Omite la iteración actual:
for (numero in 1..5) { if (numero == 3) { continue } ``` println(numero) ``` }
Resultado:
1 2 4 5
26. Rangos
Los rangos resultan especialmente útiles en los bucles.
26.1. ..
for (numero in 1..5) { println(numero) }
Recorre:
1 2 3 4 5
Los dos extremos están incluidos.
26.2. ..<
for (numero in 1..<5) { println(numero) }
Recorre:
1 2 3 4
26.3. downTo
for (numero in 5 downTo 1) { println(numero) }
26.4. step
for (numero in 0..10 step 2) { println(numero) }
Resultado:
0 2 4 6 8 10
27. Arrays
Un array almacena varios valores.
val notas = arrayOf(7, 8, 5, 9)
27.1. Índices
Los índices comienzan en `0`.
Índice: 0 1 2 3 Valor: 7 8 5 9
Podemos acceder mediante:
println(notas[0]) println(notas[2])
27.2. Modificar valores
notas[0] = 10
Aunque `notas` sea un `val`, los elementos del array pueden cambiar.
Lo que no podemos hacer es reasignar la variable:
// ERROR // notas = arrayOf(1, 2, 3)
27.3. Tamaño
println(notas.size)
27.4. Recorrer arrays
for (nota in notas) { println(nota) }
27.5. Recorrer índices
for (i in notas.indices) { println("Posición $i: ${notas[i]}") }
27.6. Índice y elemento
for ((indice, nota) in notas.withIndex()) { println("$indice -> $nota") }
28. Colecciones: List y MutableList
Además de arrays, Kotlin dispone de colecciones.
Una de las más utilizadas es `List`.
28.1. List
Una `List` de solo lectura puede crearse así:
val nombres = listOf("Ana", "Luis", "Marta")
Podemos consultar:
println(nombres[0]) println(nombres.size)
y recorrerla:
for (nombre in nombres) { println(nombre) }
Pero no podemos modificar directamente sus elementos.
28.2. MutableList
Cuando queremos añadir, eliminar o modificar elementos podemos utilizar `MutableList`.
val nombres = mutableListOf("Ana", "Luis") nombres.add("Marta") nombres.remove("Luis") println(nombres)
También:
nombres[0] = "Carlos"
En aplicaciones Kotlin y Android encontrarás con mucha frecuencia `List` y `MutableList`.
Por ahora basta con comprender la diferencia entre una colección de solo lectura y otra modificable.
29. Funciones
Una función agrupa instrucciones que realizan una determinada tarea.
fun saludar() { println("Hola") }
La ejecutamos mediante:
saludar()
Programa completo:
fun saludar() { println("Hola") } fun main() { saludar() }
29.1. ¿Por qué utilizamos funciones?
Permiten:
* Dividir un problema en partes más pequeñas. * Evitar repetir código. * Facilitar la reutilización. * Mejorar la legibilidad. * Facilitar el mantenimiento.
29.2. Parámetros
29.3. Varios parámetros
fun mostrarAlumno(nombre: String, edad: Int) { println("$nombre tiene $edad años") }
29.4. Valor de retorno
fun sumar(a: Int, b: Int): Int { return a + b }
Podemos utilizar el resultado:
val resultado = sumar(10, 5) println(resultado)
La parte:
: Int
indica el tipo devuelto por la función.
29.5. Unit
Una función que no devuelve un resultado útil tiene como retorno `Unit`.
fun saludar(): Unit { println("Hola") }
Normalmente omitimos `Unit`:
fun saludar() { println("Hola") }
29.6. Funciones de expresión única
Podemos simplificar:
fun cuadrado(numero: Int): Int { return numero * numero }
como:
fun cuadrado(numero: Int): Int = numero * numero
E incluso:
fun cuadrado(numero: Int) = numero * numero
29.7. Parámetros por defecto
fun saludar(nombre: String = "usuario") { println("Hola $nombre") }
Ahora son válidas las dos llamadas:
saludar() saludar("Ana")
29.8. Argumentos con nombre
fun crearUsuario(nombre: String, edad: Int, activo: Boolean) { println("$nombre - $edad - $activo") }
Podemos llamar:
crearUsuario( nombre = "Ana", edad = 20, activo = true )
Incluso modificar el orden:
crearUsuario( activo = true, nombre = "Ana", edad = 20 )
30. Valores nulos: null
Esta característica es especialmente importante en Kotlin.
En muchos lenguajes una variable que debería contener un objeto puede contener también `null`.
Kotlin diferencia entre:
* Tipos que no admiten `null`. * Tipos que sí admiten `null`.
Esto se denomina null safety.
30.1. Tipo no nullable
30.2. Tipo nullable
Añadimos `?`:
var nombre: String? = "Ana" nombre = null
Ahora `nombre` puede contener un `String` o `null`.
30.3. Operador ?.
Si una variable puede ser nula:
val nombre: String? = null
no deberíamos utilizar directamente:
// nombre.length
Podemos usar:
println(nombre?.length)
Si `nombre` contiene un `String`, se obtiene su longitud.
Si contiene `null`, el resultado será `null`.
30.4. Operador Elvis ?:
Permite proporcionar un valor alternativo:
val nombre: String? = null val longitud = nombre?.length ?: 0 println(longitud)
Resultado:
0
Otro ejemplo:
val nombre: String? = null val texto = nombre ?: "Sin nombre" println(texto)
30.5. Conversión segura desde String
En lugar de:
val numero = readln().toInt()
podemos utilizar:
val numero = readln().toIntOrNull()
El resultado será de tipo:
Int?
Si el texto no representa un entero válido, se devuelve `null`.
Ejemplo:
print("Introduce un número: ") val numero = readln().toIntOrNull() if (numero == null) { println("El dato introducido no es válido") } else { println("Has introducido $numero") }
La gestión de `null` es una de las características más importantes de Kotlin y aparecerá continuamente cuando comencemos a desarrollar aplicaciones Android.
Programación Orientada a Objetos
31. Introducción a la POO
La Programación Orientada a Objetos organiza los programas alrededor de objetos.
Un objeto puede contener:
* Datos, representados mediante propiedades. * Comportamiento, representado mediante funciones o métodos.
Por ejemplo, podemos representar un coche mediante:
Coche Propiedades: marca modelo velocidad Funciones: acelerar() frenar()
32. Clases y objetos
Una clase es una plantilla utilizada para crear objetos.
Un objeto es una instancia concreta de esa clase.
Podemos imaginar:
Clase: Coche Objetos: coche1 marca = Toyota modelo = Corolla coche2 marca = Seat modelo = León
33. Crear una clase
Utilizamos la palabra clave `class`:
class Coche { var marca = "" var modelo = "" var velocidad = 0 }
Por convenio, los nombres de las clases comienzan con mayúscula:
class Coche class Alumno class Producto class Usuario
34. Crear objetos
Creamos una instancia escribiendo el nombre de la clase:
val coche1 = Coche()
A diferencia de Java, Kotlin no utiliza `new`.
Podemos acceder a sus propiedades mediante `.`:
coche1.marca = "Toyota" coche1.modelo = "Corolla" println(coche1.marca)
Podemos crear tantos objetos como necesitemos:
val coche1 = Coche() val coche2 = Coche() coche1.marca = "Toyota" coche2.marca = "Seat"
35. Constructores
El constructor permite proporcionar datos cuando creamos el objeto.
En Kotlin podemos definir un constructor directamente en la cabecera:
class Persona(val nombre: String, var edad: Int)
Ahora podemos crear:
val persona1 = Persona("Ana", 20) val persona2 = Persona("Luis", 25)
Y consultar:
println(persona1.nombre) println(persona1.edad)
35.1. Propiedades val y var
class Persona( val nombre: String, var edad: Int )
`nombre` no puede ser reasignado:
// persona1.nombre = "Luis" // ERROR
Pero `edad` sí:
persona1.edad = 21
35.2. Valores por defecto
Podemos proporcionar valores:
class Usuario( val nombre: String, var activo: Boolean = true )
Ahora podemos crear:
val usuario1 = Usuario("Ana") val usuario2 = Usuario("Luis", false)
36. Bloque init
Podemos ejecutar código al crear un objeto utilizando `init`.
class Persona( val nombre: String, var edad: Int ) { init { println("Se ha creado la persona $nombre") } }
Al ejecutar:
val persona = Persona("Ana", 20)
se ejecutará automáticamente el bloque `init`.
También puede utilizarse para validar información sencilla:
class Producto( val nombre: String, val precio: Double ) { init { if (precio < 0) { println("Advertencia: precio incorrecto") } } }
37. Funciones de una clase
Una clase puede contener funciones.
class Persona( val nombre: String, var edad: Int ) { ``` fun saludar() { println("Hola, me llamo $nombre") } fun cumplirAnios() { edad++ } ``` }
Uso:
val persona = Persona("Ana", 20) persona.saludar() persona.cumplirAnios() println(persona.edad)
38. Ejemplo: clase CuentaBancaria
class CuentaBancaria( val titular: String, var saldo: Double ) { ``` fun ingresar(cantidad: Double) { if (cantidad > 0) { saldo += cantidad } } fun retirar(cantidad: Double) { if (cantidad > 0 && cantidad <= saldo) { saldo -= cantidad } } fun mostrarSaldo() { println("Saldo de $titular: $saldo €") } ``` }
Podemos utilizarla:
fun main() { ``` val cuenta = CuentaBancaria("Ana", 1000.0) cuenta.ingresar(500.0) cuenta.retirar(200.0) cuenta.mostrarSaldo() ``` }
39. Encapsulación
Uno de los principios de la POO consiste en controlar qué partes internas de un objeto pueden modificarse desde fuera.
Consideremos:
class CuentaBancaria( val titular: String, var saldo: Double )
Cualquier parte del programa podría hacer:
cuenta.saldo = -50000.0
Algo que probablemente no queremos permitir.
Podemos hacer que `saldo` sea privado:
class CuentaBancaria( val titular: String, private var saldo: Double ) { ``` fun ingresar(cantidad: Double) { if (cantidad > 0) { saldo += cantidad } } fun mostrarSaldo() { println(saldo) } ``` }
Ahora:
// cuenta.saldo = -500.0
no está permitido desde fuera de la clase.
40. Modificadores de visibilidad
Los modificadores principales son:
| Modificador | Significado básico |
|---|---|
| `public` | Accesible desde cualquier lugar |
| `private` | Accesible solamente dentro de la clase o ámbito correspondiente |
| `protected` | Accesible desde la clase y sus subclases |
| `internal` | Accesible dentro del mismo módulo |
Si no indicamos ninguno, normalmente la visibilidad es `public`.
Para esta introducción utilizaremos principalmente:
* `public` * `private`
41. Herencia
La herencia permite crear una clase a partir de otra.
Por ejemplo:
Persona | +-- Alumno | +-- Profesor
La clase `Persona` contiene características comunes.
`Alumno` y `Profesor` pueden reutilizarlas.
42. open
En Kotlin las clases son finales por defecto.
Esto significa que no podemos heredar de ellas salvo que lo permitamos expresamente.
Utilizamos `open`:
open class Persona( val nombre: String, val edad: Int )
Ahora podemos crear:
class Alumno( nombre: String, edad: Int, val curso: String ) : Persona(nombre, edad)
Uso:
val alumno = Alumno( "Ana", 20, "DAM" ) println(alumno.nombre) println(alumno.edad) println(alumno.curso)
43. Sobrescritura de funciones
Una clase hija puede modificar el comportamiento de una función de la clase padre.
Para permitirlo, la función original debe ser `open`.
open class Persona( val nombre: String ) { ``` open fun presentarse() { println("Soy $nombre") } ``` }
La subclase utiliza `override`:
class Alumno( nombre: String, val curso: String ) : Persona(nombre) { ``` override fun presentarse() { println("Soy $nombre y estudio $curso") } ``` }
Uso:
val alumno = Alumno("Ana", "DAM") alumno.presentarse()
44. super
Podemos llamar a la implementación de la clase padre mediante `super`.
class Alumno( nombre: String, val curso: String ) : Persona(nombre) { ``` override fun presentarse() { super.presentarse() println("Estudio $curso") } ``` }
45. Clases abstractas
En algunas situaciones queremos definir una clase general que no tenga sentido instanciar directamente.
Por ejemplo:
Figura | +-- Circulo +-- Rectangulo
Podemos declarar:
abstract class Figura { abstract fun calcularArea(): Double }
No podemos hacer:
// val figura = Figura() // ERROR
Una subclase debe implementar la función:
class Rectangulo( val base: Double, val altura: Double ) : Figura() { ``` override fun calcularArea(): Double { return base * altura } ``` }
46. Interfaces
Una interfaz define un conjunto de operaciones que una clase debe proporcionar.
interface Imprimible { fun imprimir() }
Una clase puede implementar la interfaz:
class Documento( val titulo: String ) : Imprimible { ``` override fun imprimir() { println("Imprimiendo $titulo") } ``` }
Una clase puede implementar varias interfaces.
Esto permite expresar capacidades:
Imprimible Guardable Serializable ...
sin obligarnos a crear una jerarquía de herencia de clases.
47. data class
En muchas aplicaciones necesitamos clases cuya función principal sea almacenar información.
Por ejemplo:
data class Alumno( val nombre: String, val edad: Int, val nota: Double )
Podemos crear:
val alumno = Alumno( "Ana", 20, 8.5 )
Una `data class` proporciona automáticamente diversas funciones útiles.
Por ejemplo:
println(alumno)
mostrará una representación legible del objeto.
También podemos realizar una copia modificando algunos valores:
val alumno2 = alumno.copy(nota = 9.0)
Las `data class` son extremadamente habituales en aplicaciones Kotlin y Android, porque permiten representar cómodamente datos como usuarios, productos, mensajes, tareas, etc.
48. Ejemplo completo de POO
open class Persona( val nombre: String, val edad: Int ) { ``` open fun mostrarInformacion() { println("Nombre: $nombre") println("Edad: $edad") } ``` } class Alumno( nombre: String, edad: Int, val ciclo: String, val nota: Double ) : Persona(nombre, edad) { ``` override fun mostrarInformacion() { super.mostrarInformacion() println("Ciclo: $ciclo") println("Nota: $nota") } fun estaAprobado(): Boolean { return nota >= 5 } ``` } fun main() { ``` val alumno = Alumno( "Ana", 20, "DAM", 8.25 ) alumno.mostrarInformacion() if (alumno.estaAprobado()) { println("El alumno está aprobado") } else { println("El alumno está suspenso") } ``` }
Ejercicios
49. Ejercicio 1 — Conversor de temperaturas
Crea un programa de consola que permita introducir una temperatura en grados Celsius.
El programa debe calcular y mostrar:
* Temperatura en Fahrenheit. * Temperatura en Kelvin.
Utiliza las fórmulas:
F = C * 9 / 5 + 32 K = C + 273.15
Ejemplo:
Introduce temperatura en ºC: 25 25.0 ºC equivalen a: 77.0 ºF 298.15 K
Requisitos
* La temperatura debe almacenarse como `Double`. * Deben utilizarse variables adecuadamente nombradas. * Los cálculos deben almacenarse en variables antes de mostrarlos.
Ampliación
Pregunta al usuario qué conversión desea realizar:
1. Celsius a Fahrenheit 2. Celsius a Kelvin 3. Fahrenheit a Celsius 4. Salir
Utiliza `when`.
50. Ejercicio 2 — Calculadora de nómina sencilla
Solicita:
* Nombre del trabajador. * Número de horas trabajadas. * Precio por hora.
Calcula el salario bruto.
Si el trabajador ha realizado más de 40 horas, las horas que excedan de 40 se pagarán al 150 % del precio normal.
Por ejemplo:
Trabajador: Ana Horas trabajadas: 45 Precio/hora: 15 Horas normales: 40 Horas extra: 5 Salario bruto: 712.50 €
Divide el programa utilizando al menos una función para calcular el salario.
51. Ejercicio 3 — Juego de adivinar el número
El programa debe almacenar un número secreto entre 1 y 100.
El usuario irá introduciendo números hasta acertarlo.
Después de cada intento deberá mostrar:
El número buscado es mayor
o:
El número buscado es menor
Cuando acierte:
¡Correcto! Has necesitado 7 intentos.
Requisitos
Utilizar:
* `while` o `do…while`. * `if`. * Un contador. * Conversión del dato introducido.
Ampliación
Evita que una entrada que no sea numérica termine el programa utilizando `toIntOrNull()`.
52. Ejercicio 4 — Estadísticas de notas
Crea una colección con las notas:
val notas = listOf(7.5, 4.0, 8.5, 6.0, 3.5, 9.0, 5.5, 2.0)
Calcula manualmente mediante bucles:
* Suma de todas las notas. * Nota media. * Nota máxima. * Nota mínima. * Número de aprobados. * Número de suspensos.
La salida podría ser:
Número de alumnos: 8 Media: 5.75 Nota máxima: 9.0 Nota mínima: 2.0 Aprobados: 5 Suspensos: 3
No utilices todavía funciones como `average()`, `max()` o `min()`.
El objetivo es practicar bucles, condiciones y variables.
53. Ejercicio 5 — Cajero automático
Realiza una aplicación de consola que simule una cuenta bancaria.
El saldo inicial será:
var saldo = 1000.0
El programa mostrará repetidamente:
----- CAJERO ----- 1. Consultar saldo 2. Ingresar dinero 3. Retirar dinero 4. Salir Selecciona una opción:
Requisitos:
* Utilizar un bucle para mantener el menú activo. * Utilizar `when` para gestionar las opciones. * No permitir ingresos negativos. * No permitir retirar una cantidad superior al saldo. * Mostrar mensajes de error cuando corresponda. * Finalizar únicamente cuando el usuario seleccione `4`.
Ampliación
Divide el programa en funciones.
54. Ejercicio 6 — Gestión de alumnos mediante funciones
Crea un programa que permita almacenar las notas de varios alumnos.
Para simplificar, utiliza inicialmente dos listas:
val nombres = mutableListOf<String>() val notas = mutableListOf<Double>()
El menú será:
1. Añadir alumno 2. Mostrar alumnos 3. Mostrar aprobados 4. Mostrar suspensos 5. Mostrar nota media 6. Salir
Implementa al menos las funciones:
fun mostrarAlumnos(...) fun mostrarAprobados(...) fun mostrarSuspensos(...) fun calcularMedia(...): Double
55. Ejercicio 7 — Clase Producto
Crea una clase:
Producto
con las propiedades:
nombre precio stock
Debe contener las funciones:
mostrarInformacion()
vender(cantidad)
reponer(cantidad)
```
`vender()` debe:
* Comprobar que la cantidad sea positiva.
* Comprobar que exista stock suficiente.
* Reducir el stock si la venta puede realizarse.
* Mostrar un error en caso contrario.
Ejemplo:
<code kotlin>
val producto = Producto(
"Teclado",
39.95,
10
)
producto.vender(3)
producto.reponer(5)
producto.mostrarInformacion()
56. Ejercicio 8 — Cuenta bancaria orientada a objetos
Crea una clase `CuentaBancaria` con:
Propiedades:
titular saldo
El saldo debe ser privado.
Funciones:
consultarSaldo() ingresar(cantidad) retirar(cantidad)
Reglas:
* No puede ingresarse una cantidad negativa. * No puede retirarse una cantidad negativa. * No puede retirarse más dinero que el disponible.
Después crea al menos dos cuentas y realiza diferentes operaciones.
57. Ejercicio 9 — Herencia: empleados
Crea la clase:
open class Empleado( val nombre: String, val salarioBase: Double )
Debe contener:
open fun calcularSalario(): Double
Después crea:
Programador Comercial ``` `Programador` tendrá además: <code> horasExtra precioHoraExtra ``` Su salario será: <code> salarioBase + horasExtra * precioHoraExtra
`Comercial` tendrá:
ventas porcentajeComision ``` Su salario será: <code> salarioBase + ventas * porcentajeComision
Ambas clases deberán sobrescribir `calcularSalario()`.
Crea varios objetos y muestra sus salarios.
58. Ejercicio 10 — Sistema de figuras
Crea una clase abstracta:
abstract class Figura { abstract fun calcularArea(): Double abstract fun calcularPerimetro(): Double }
Implementa:
* `Rectangulo` * `Circulo`
Cada clase deberá calcular correctamente área y perímetro.
Posteriormente crea varios objetos y muestra sus resultados.
Ampliación
Añade:
* `Cuadrado` * `Triangulo`
59. Práctica final — Gestión de una biblioteca
Desarrolla una pequeña aplicación de consola para gestionar una biblioteca.
Parte 1. Modelo de datos
Crea una `data class`:
data class Libro( val titulo: String, val autor: String, val isbn: String, var disponible: Boolean = true )
Parte 2. Biblioteca
Crea una clase:
class Biblioteca
que internamente almacene:
private val libros = mutableListOf<Libro>()
Debe disponer al menos de las siguientes funciones:
fun agregarLibro(libro: Libro) fun mostrarLibros() fun mostrarDisponibles() fun buscarPorTitulo(titulo: String) fun prestarLibro(isbn: String) fun devolverLibro(isbn: String)
Parte 3. Menú
El programa principal debe mostrar:
========= BIBLIOTECA ========= 1. Añadir libro 2. Mostrar todos los libros 3. Mostrar libros disponibles 4. Buscar libro 5. Prestar libro 6. Devolver libro 7. Salir Seleccione una opción:
El menú debe repetirse hasta seleccionar la opción `7`.
Requisitos
La práctica debe utilizar:
* `val` y `var`. * Tipos básicos. * Strings. * Operadores. * `if`. * `when`. * Bucles. * Funciones. * Colecciones. * Null-safety cuando resulte necesario. * Clases. * Constructores. * Encapsulación. * `data class`.
Ampliaciones
Añade una clase `Usuario`:
data class Usuario( val id: Int, val nombre: String )
Permite posteriormente registrar qué usuario tiene prestado cada libro.
Añade también:
* Listado de libros prestados. * Número total de libros. * Número de libros disponibles. * Número de libros prestados. * Búsqueda por autor.
Resumen de sintaxis
Variables
val nombre = "Ana" var edad = 20
Tipos
val numero: Int = 10 val grande: Long = 100L val decimal: Double = 3.14 val decimalFloat: Float = 3.14f val activo: Boolean = true val letra: Char = 'A' val texto: String = "Hola"
Entrada
val texto = readln() val numero = readln().toInt() val decimal = readln().toDouble()
Conversión segura
val numero: Int? = readln().toIntOrNull()
Condicional
if (condicion) { // ... } else { // ... }
when
when (opcion) { 1 -> println("Uno") 2 -> println("Dos") else -> println("Otra opción") }
while
while (condicion) { // ... }
for
for (i in 1..10) { println(i) }
Array
val numeros = arrayOf(10, 20, 30)
Lista
val nombres = listOf("Ana", "Luis")
Lista modificable
val nombres = mutableListOf("Ana", "Luis") nombres.add("Marta")
Función
fun saludar(nombre: String) { println("Hola $nombre") }
Función con retorno
fun sumar(a: Int, b: Int): Int { return a + b }
Clase
class Persona( val nombre: String, var edad: Int )
Crear objeto
val persona = Persona("Ana", 20)
Método
class Persona( val nombre: String ) { fun saludar() { println("Hola, soy $nombre") } }
Herencia
open class Persona( val nombre: String ) class Alumno( nombre: String, val curso: String ) : Persona(nombre)
Sobrescritura
open class Persona { open fun saludar() { println("Hola") } } class Alumno : Persona() { ``` override fun saludar() { println("Hola desde Alumno") } ``` }
Data class
data class Usuario( val nombre: String, val edad: Int )
Nullable
var nombre: String? = null println(nombre?.length) val texto = nombre ?: "Sin nombre"
Antes de empezar Android
Antes de comenzar el desarrollo de aplicaciones Android deberías ser capaz de interpretar y escribir programas sencillos que utilicen:
* `val` y `var`. * Inferencia de tipos. * `Int`, `Long`, `Float`, `Double`, `Boolean`, `Char` y `String`. * Conversiones entre tipos. * Interpolación de cadenas. * Operadores aritméticos, lógicos y de comparación. * `if`. * `when`. * `while`. * `for`. * Rangos. * Arrays. * `List` y `MutableList`. * Funciones. * Parámetros y valores de retorno. * Tipos nullable y operadores `?.` y `?:`. * Clases y objetos. * Constructores. * Propiedades. * Métodos. * Encapsulación básica. * Herencia. * `open` y `override`. * Clases abstractas e interfaces. * `data class`.
No es necesario memorizar toda la sintaxis.
El objetivo es comprender los conceptos, saber interpretar código Kotlin y ser capaz de localizar y aplicar la sintaxis necesaria para resolver un problema.
