Condiciones: if y when
Al terminar: Decidir qué hace el programa con if, else y when, y combinar condiciones con && y ||.
Al terminar sabrás hacer que el programa decida con preguntas de sí o no. Con eso tendrás la primera regla del torneo en código: puntosPor(golesAFavor, golesEnContra), que dice cuántos puntos gana un equipo con un marcador.
Importa porque casi todo el torneo es esto: una función que recibe datos y decide.
Temas: condiciones (>, ==, &&, ||, !); if y when, y cómo se devuelve un valor desde ellos. En la lección siguiente, Bucles y tu primer test, repetirás cosas con bucles y comprobarás esta regla con un test.
Condiciones: preguntas de sí o no
Para que un programa decida, primero tiene que poder preguntar. Una condición es una pregunta cuya respuesta es sí o no, es decir, un valor Boolean (true o false, que ya conoces de Variables, tipos y operaciones). Se construyen comparando valores o combinando otras condiciones:
| Se escribe | Pregunta | Ejemplo | Da |
|---|---|---|---|
> y < | ¿mayor que? ¿menor que? | 3 > 2 | true |
>= y <= | ¿mayor o igual? ¿menor o igual? | 3 >= 3 | true |
== | ¿es igual? | 3 == 4 | false |
!= | ¿es distinto? | 3 != 4 | true |
&& | ¿se cumplen las dos? («y») | 3 > 2 && 3 < 5 | true |
|| | ¿se cumple al menos una? («o») | 3 > 5 || 3 > 2 | true |
! | lo contrario | !(3 > 2) | false |
Ojo con el =: un signo guarda un valor en una variable (val edad = 15) y dos signos preguntan si dos valores son iguales (edad == 15).
fun main() {
val edad = 15
val conAutorizacion = true
println(edad > 18) // false: ¿mayor que?
println(edad >= 15) // true: ¿mayor o igual?
println(edad == 15) // true: ¿igual? (dos signos; uno solo guarda un valor)
println(edad != 15) // false: ¿distinto?
println(edad >= 18 || conAutorizacion) // true: || es «o»: basta con que una sea verdad
println(edad >= 12 && conAutorizacion) // true: && es «y»: tienen que serlo las dos
println(!conAutorizacion) // false: ! le da la vuelta
}false
true
true
false
true
true
falseif: decidir entre dos caminos
if (condición) { … } else { … } ejecuta el primer bloque si la condición es verdadera y el segundo si no. La parte else if (otra condición) añade más caminos; el else final recoge el resto.
Un if puede usarse de dos formas. Antes, la diferencia entre instrucción y expresión: una instrucción hace algo (imprimir, guardar), y una expresión da un valor (x * 2, edad > 18). El if puede ser lo primero, ejecutar una cosa u otra, y también lo segundo: lo que valga la rama elegida es lo que devuelve el if, y lo puedes guardar en una variable, pasar a println o devolver desde una función.
fun main() {
val puntos = 3
// if como instrucción: ejecuta una cosa u otra, no da ningún valor
if (puntos > 1) {
println("Varios puntos")
} else {
println("Un punto o ninguno")
}
// if como expresión: lo que valga la rama elegida se guarda en la variable
val texto = if (puntos > 1) "puntos" else "punto"
println("Tienes $puntos $texto")
// también dentro de una llamada, y en cadena con else if
println(if (puntos == 0) "Sin puntos" else if (puntos < 3) "Pocos" else "Muchos")
// con llaves, el valor de la rama es su última línea (aquí, el texto)
val mensaje = if (puntos >= 3) {
val extra = puntos - 3
"Tres o más (sobran $extra)"
} else {
"Menos de tres"
}
println(mensaje)
}Varios puntos
Tienes 3 puntos
Muchos
Tres o más (sobran 0)Como el if tiene que dar un valor en cualquier caso, el else es obligatorio cuando lo usas como expresión.
when: elegir entre muchos caminos
Cuando hay más de dos caminos, una cadena de else if se hace larga; when la ordena. Cada rama se escribe lo que se pregunta -> el valor que se da: a la izquierda de la flecha -> va la condición y a la derecha el valor que devuelve esa rama. Tiene dos formas. Sin sujeto, when { … }, cada rama es una condición completa y se aplica la primera que sea verdadera: es la que usarás para la regla de los puntos. Con sujeto, when (minuto) { … }, compara ese valor con cada rama. Como el if, when también da un valor.
// Sin sujeto: cada rama es una condición completa; gana la primera que sea verdadera
fun clima(temperatura: Int): String = when {
temperatura < 10 -> "frío"
temperatura < 25 -> "templado"
else -> "calor" // si ninguna anterior encaja, esta
}
// Con sujeto: compara minuto con cada rama; «in 0..45» significa «está entre 0 y 45»
fun tramo(minuto: Int): String = when (minuto) {
in 0..45 -> "primer tiempo"
in 46..90 -> "segundo tiempo"
else -> "prórroga"
}
fun main() {
println(clima(5))
println(clima(20))
println(clima(31))
val minuto = 47
val etiqueta = if (minuto > 45) "segundo" else "primero"
println("$etiqueta: ${tramo(minuto)}")
}frío
templado
calor
segundo: segundo tiempoin 0..45 es un rango, los números del 0 al 45; los rangos se explican en Bucles y tu primer test. De momento léelo como «está entre 0 y 45».
Sin else, cada una de estas dos formas falla al compilar. Con val x = if (minuto > 45) 3 sale 'if' must have both main and 'else' branches when used as an expression., y con un when incompleto, 'when' expression must be exhaustive. Add an 'else' branch.
Las funciones clima y tramo de arriba son de expresión: el when es todo el cuerpo y su valor es el resultado. Con llaves hay que decir qué se devuelve con return: puede ir delante del when entero, o en cada rama de unos if sueltos.
// Con llaves: return delante del when entero (devuelve lo que valga la rama que encaje)
fun climaConLlaves(temperatura: Int): String {
return when {
temperatura < 10 -> "frío"
temperatura < 25 -> "templado"
else -> "calor"
}
}
// Con if sueltos: cada return devuelve y termina la función; si no se cumple ninguno, llega al último
fun climaConIf(temperatura: Int): String {
if (temperatura < 10) {
return "frío" // si entra aquí, la función acaba y lo de abajo no se ejecuta
}
if (temperatura < 25) {
return "templado"
}
return "calor" // el caso que queda: obligatorio, o no compila
}
fun main() {
println(climaConLlaves(30)) // calor
println(climaConIf(5)) // frío
println(climaConIf(18)) // templado
}calor
frío
templadoSi una función declara un tipo de retorno (: Int, : String) y algún camino acaba sin return, no compila: por eso el último return de climaConIf es obligatorio.
En resumen: con la forma corta (= y la expresión, sin llaves), if y when dan el valor directamente. Con llaves, la función necesita return: uno delante del when entero, o uno en cada camino de los if sueltos.
Reto · Ejercicio: el parque de atracciones
En el Kotlin Playground (el mismo de Tu primer proyecto en Kotlin; no toques tu proyecto), escribe tres funciones. tarifa recibe la edad de un visitante y devuelve lo que paga por un día: hasta 5 años, 0; hasta 17, 8; hasta 64, 15; a partir de 65, 10. total recibe la edad y los días que viene, que si no se dice es 1, y devuelve lo que paga en total. etiqueta recibe un precio y devuelve «gratis» si es 0 o «de pago» si no. En main, escribe una línea para las edades 4, 10, 30 y 70 (un día cada una), y otra para la edad 30 durante 3 días, pasando los días por su nombre. La consola debe mostrar:
Edad 4: 0 (gratis)
Edad 10: 8 (de pago)
Edad 30: 15 (de pago)
Edad 70: 10 (de pago)
Edad 30 durante 3 días: 45Ver solución
fun tarifa(edad: Int): Int = when { // when sin sujeto: cada rama es una condición
edad <= 5 -> 0
edad <= 17 -> 8 // llega aquí solo si no se cumplió la anterior
edad <= 64 -> 15
else -> 10 // la rama final es obligatoria
}
// Valor por defecto: si no se dicen los días, es 1
fun total(edad: Int, dias: Int = 1): Int = tarifa(edad) * dias
fun etiqueta(precio: Int): String = if (precio == 0) "gratis" else "de pago" // if como expresión
fun main() {
println("Edad 4: ${total(4)} (${etiqueta(tarifa(4))})") // ${…}: el resultado de una llamada, dentro del texto
println("Edad 10: ${total(10)} (${etiqueta(tarifa(10))})")
println("Edad 30: ${total(30)} (${etiqueta(tarifa(30))})")
println("Edad 70: ${total(70)} (${etiqueta(tarifa(70))})")
println("Edad 30 durante 3 días: ${total(30, dias = 3)}") // argumento con nombre
}Paso · puntosPor en Puntos.kt y un Main.kt que lo usa
Objetivo: añadir la regla de los puntos a Puntos.kt y que Main.kt la llame. Al terminar, ./gradlew :torneo:run imprime el torneo, sus jornadas y unos cuantos marcadores con sus puntos. Los tests de esta regla los escribes en la lección siguiente.
1 · Abre Puntos.kt. En la vista Android, despliega torneo → kotlin+java → com.sebastiannarvaez.marcador.torneo y haz doble clic en Puntos.kt.
Piezas que vas a usar, todas de esta lección:
fun nombre(a: Int, b: Int): Int = …: función de expresión; lo que va tras el=es lo que devuelve.when { condición -> valor … else -> valor }: elwhensin sujeto.if (cond) a else bdentro de unprintln(…): elifcomo expresión.
Intenta escribirlo tú, en dos ficheros:
Puntos.kt: añadepuntosPor(golesAFavor, golesEnContra), que devuelve los puntos de un partido: 3 si ganas, 1 si empatas y 0 si pierdes. Ponla encima deencabezadoDeJornada.Main.kt: sustituye todo su contenido. El programa presenta el torneo («Copa Barrio»), escribe el encabezado de las jornadas 1, 2 y 3 de 3 (con argumentos con nombre) y uno más con el prefijo «Fecha», escribe los puntos del local para los marcadores 2-1, 1-1 y 0-3, y dice cuánto vale un empate.
Al ejecutar ./gradlew :torneo:run debe verse:
Torneo: Copa Barrio
Jornada 1 de 3
Jornada 2 de 3
Jornada 3 de 3
Fecha 1 de 3
2-1 -> 3 puntos para el local
1-1 -> 1 puntos para el local
0-3 -> 0 puntos para el local
Un empate vale 1 puntoEl título de cada bloque de abajo es la ruta del fichero en tu proyecto. En cada uno, selecciona todo (Cmd + A) y pega el bloque.
Intenta primero. Cuando termines, compara con la solución.Mostrar el código
package com.sebastiannarvaez.marcador.torneo
// Función suelta: escrita directamente en el fichero.
// Función de expresión: el cuerpo es un único `when` que devuelve el valor.
fun puntosPor(golesAFavor: Int, golesEnContra: Int): Int = when {
golesAFavor > golesEnContra -> 3
golesAFavor == golesEnContra -> 1
else -> 0
}
// Parámetros con valor por defecto: se pueden omitir al llamar.
fun encabezadoDeJornada(numero: Int, totalDeJornadas: Int, prefijo: String = "Jornada"): String =
"$prefijo $numero de $totalDeJornadas"package com.sebastiannarvaez.marcador.torneo
// Punto de entrada de consola: ./gradlew :torneo:run
fun main() {
val nombreDelTorneo = "Copa Barrio"
val totalDeJornadas = 3
println("Torneo: $nombreDelTorneo")
// Argumento con nombre: se lee sin adivinar qué es cada número.
println(encabezadoDeJornada(numero = 1, totalDeJornadas = totalDeJornadas))
println(encabezadoDeJornada(numero = 2, totalDeJornadas = totalDeJornadas))
println(encabezadoDeJornada(numero = 3, totalDeJornadas = totalDeJornadas))
println(encabezadoDeJornada(1, 3, prefijo = "Fecha"))
// Los puntos del local para tres marcadores.
println("2-1 -> ${puntosPor(2, 1)} puntos para el local")
println("1-1 -> ${puntosPor(1, 1)} puntos para el local")
println("0-3 -> ${puntosPor(0, 3)} puntos para el local")
// `if` como expresión.
val hayEmpates = puntosPor(1, 1) == 1
println(if (hayEmpates) "Un empate vale 1 punto" else "Sin empates")
}Lo que ves en Puntos.kt: puntosPor es una función suelta y de expresión, con un when sin sujeto, así que sus ramas son condiciones y necesita else.
Punto de control
- En la vista Android, el paquete
com.sebastiannarvaez.marcador.torneodetorneo → kotlin+javacontieneMain.ktyPuntos.kt. ./gradlew :torneo:runimprime las nueve líneas de arriba, deTorneo: Copa BarrioaUn empate vale 1 punto.Puntos.kttiene una función de expresión conwheny una con valor por defecto. Todavía no hay tests: no hay nada que contar hasta la lección siguiente, donde aprenderás a abrir el informe de tests.
Comprueba lo que entendiste
4 preguntas · necesitas 3 para completar la lección
1.Una función
puntosPor(a: Int, b: Int): Intusa unwhensin sujeto con tres ramas: sia > bda 3, sia == bda 1 y en el resto de casos (else) da 0. ¿Qué devuelve conpuntosPor(0, 2)?2.¿Qué pasa si escribes
val x = if (minuto > 45) 3sinelse?3.Con
val edad = 15yval conPermiso = true, ¿qué daedad >= 18 || conPermiso?4.Con
var edad = 15, ¿qué diferencia hay entreedad = 20yedad == 20?