Herramientas de usuario

Herramientas del sitio


extra:guia

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

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<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.

extra/guia.txt · Última modificación: por fernando