====== 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 ====
fun saludar(nombre: String) {
println("Hola $nombre")
}
Llamadas:
saludar("Ana")
saludar("Pedro")
==== 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 ====
var nombre: String = "Ana"
No podemos hacer:
// ERROR
// nombre = null
==== 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()
val notas = mutableListOf()
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:
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:
horasExtra
precioHoraExtra
```
Su salario será:
salarioBase + horasExtra * precioHoraExtra
`Comercial` tendrá:
ventas
porcentajeComision
```
Su salario será:
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()
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.