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.
Un programa Kotlin sencillo puede escribirse de la siguiente forma:
fun main() { println("Hola mundo") }
El resultado será:
Hola mundo
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") }
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.
Los comentarios permiten introducir explicaciones en el código.
El compilador los ignora.
// Esto es un comentario val edad = 20
También podemos escribirlo después de una instrucción:
val edad = 20 // Edad del alumno
/*
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.
Kotlin proporciona las funciones `print()` y `println()`.
Muestra un valor y después realiza un salto de línea.
println("Uno") println("Dos") println("Tres")
Resultado:
Uno Dos Tres
Muestra un valor sin realizar un salto de línea.
print("Hola ") print("mundo")
Resultado:
Hola mundo
Una variable es un espacio que utilizamos para almacenar información.
En Kotlin existen dos formas principales de declarar variables:
* `val` * `var`
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
Una variable declarada mediante `var` sí puede ser reasignada.
var edad = 20 edad = 21 edad = 22 println(edad)
Resultado:
22
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`.
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.
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 |
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 |
Es el tipo entero que utilizaremos habitualmente.
val edad: Int = 20 val alumnos: Int = 28 val temperatura: Int = -4
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
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.
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.
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 |
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
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.
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
`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`.
Un `String` representa una cadena de caracteres.
val nombre = "Carlos" val ciudad = "León" val mensaje = "Aprendiendo Kotlin"
Podemos obtener la longitud mediante `length`:
val lenguaje = "Kotlin" println(lenguaje.length)
Resultado:
6
Los caracteres están numerados comenzando por `0`.
val lenguaje = "Kotlin" println(lenguaje[0]) println(lenguaje[1])
Resultado:
K o
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.
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 `+`.
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.
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()
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`.
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") ``` }
`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.
| 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)
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)
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") }
var numero = 10 numero += 5 numero -= 2 numero *= 3 numero /= 2
Por ejemplo:
numero += 5
es una forma abreviada de expresar:
numero = numero + 5
Podemos incrementar una variable:
var contador = 0 contador++
o decrementarla:
contador--
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)
`=` realiza una asignación:
var edad = 20 edad = 21
`==` compara valores:
if (edad == 21) { println("Tiene 21 años") }
| Operador | Significado | ||
|---|---|---|---|
| `&&` | AND | ||
| ` | ` | OR | |
| `!` | NOT | ||
Las dos condiciones deben cumplirse:
val edad = 20 val tieneCarnet = true if (edad >= 18 && tieneCarnet) { println("Puede conducir") }
Debe cumplirse al menos una:
val esSabado = true val esDomingo = false if (esSabado || esDomingo) { println("Es fin de semana") }
Invierte un booleano:
val activo = true println(!activo)
Resultado:
false
Permite ejecutar código cuando se cumple una condición.
if (condicion) { // instrucciones }
Ejemplo:
val edad = 20 if (edad >= 18) { println("Mayor de edad") }
if (edad >= 18) { println("Mayor de edad") } else { println("Menor de edad") }
val nota = 7 if (nota >= 9) { println("Sobresaliente") } else if (nota >= 7) { println("Notable") } else if (nota >= 5) { println("Aprobado") } else { println("Suspenso") }
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.
`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") }
val dia = 6 when (dia) { 1, 2, 3, 4, 5 -> println("Laborable") 6, 7 -> println("Fin de semana") else -> println("Valor incorrecto") }
val nota = 8 val calificacion = when (nota) { 9, 10 -> "Sobresaliente" 7, 8 -> "Notable" 5, 6 -> "Aprobado" else -> "Suspenso" } println(calificacion)
val edad = 35 when { edad < 18 -> println("Menor de edad") edad < 65 -> println("Adulto") else -> println("Mayor de 65 años") }
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
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.
`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.
Finaliza el bucle:
for (numero in 1..10) { if (numero == 5) { break } ``` println(numero) ``` }
Resultado:
1 2 3 4
Omite la iteración actual:
for (numero in 1..5) { if (numero == 3) { continue } ``` println(numero) ``` }
Resultado:
1 2 4 5
Los rangos resultan especialmente útiles en los bucles.
for (numero in 1..5) { println(numero) }
Recorre:
1 2 3 4 5
Los dos extremos están incluidos.
for (numero in 1..<5) { println(numero) }
Recorre:
1 2 3 4
for (numero in 5 downTo 1) { println(numero) }
for (numero in 0..10 step 2) { println(numero) }
Resultado:
0 2 4 6 8 10
Un array almacena varios valores.
val notas = arrayOf(7, 8, 5, 9)
Los índices comienzan en `0`.
Índice: 0 1 2 3 Valor: 7 8 5 9
Podemos acceder mediante:
println(notas[0]) println(notas[2])
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)
println(notas.size)
for (nota in notas) { println(nota) }
for (i in notas.indices) { println("Posición $i: ${notas[i]}") }
for ((indice, nota) in notas.withIndex()) { println("$indice -> $nota") }
Además de arrays, Kotlin dispone de colecciones.
Una de las más utilizadas es `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.
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.
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() }
Permiten:
* Dividir un problema en partes más pequeñas. * Evitar repetir código. * Facilitar la reutilización. * Mejorar la legibilidad. * Facilitar el mantenimiento.
fun mostrarAlumno(nombre: String, edad: Int) { println("$nombre tiene $edad años") }
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.
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") }
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
fun saludar(nombre: String = "usuario") { println("Hola $nombre") }
Ahora son válidas las dos llamadas:
saludar() saludar("Ana")
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 )
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.
Añadimos `?`:
var nombre: String? = "Ana" nombre = null
Ahora `nombre` puede contener un `String` o `null`.
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`.
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)
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.
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()
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
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
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"
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)
class Persona( val nombre: String, var edad: Int )
`nombre` no puede ser reasignado:
// persona1.nombre = "Luis" // ERROR
Pero `edad` sí:
persona1.edad = 21
Podemos proporcionar valores:
class Usuario( val nombre: String, var activo: Boolean = true )
Ahora podemos crear:
val usuario1 = Usuario("Ana") val usuario2 = Usuario("Luis", false)
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") } } }
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)
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() ``` }
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.
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`
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.
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)
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()
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") } ``` }
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 } ``` }
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.
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.
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") } ``` }
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`.
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.
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()`.
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.
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.
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
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()
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.
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.
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`
Desarrolla una pequeña aplicación de consola para gestionar una biblioteca.
Crea una `data class`:
data class Libro( val titulo: String, val autor: String, val isbn: String, var disponible: Boolean = true )
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)
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`.
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`.
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.
val nombre = "Ana" var edad = 20
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"
val texto = readln() val numero = readln().toInt() val decimal = readln().toDouble()
val numero: Int? = readln().toIntOrNull()
if (condicion) { // ... } else { // ... }
when (opcion) { 1 -> println("Uno") 2 -> println("Dos") else -> println("Otra opción") }
while (condicion) { // ... }
for (i in 1..10) { println(i) }
val numeros = arrayOf(10, 20, 30)
val nombres = listOf("Ana", "Luis")
val nombres = mutableListOf("Ana", "Luis") nombres.add("Marta")
fun saludar(nombre: String) { println("Hola $nombre") }
fun sumar(a: Int, b: Int): Int { return a + b }
class Persona( val nombre: String, var edad: Int )
val persona = Persona("Ana", 20)
class Persona( val nombre: String ) { fun saludar() { println("Hola, soy $nombre") } }
open class Persona( val nombre: String ) class Alumno( nombre: String, val curso: String ) : Persona(nombre)
open class Persona { open fun saludar() { println("Hola") } } class Alumno : Persona() { ``` override fun saludar() { println("Hola desde Alumno") } ``` }
data class Usuario( val nombre: String, val edad: Int )
var nombre: String? = null println(nombre?.length) val texto = nombre ?: "Sin nombre"
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.