if como expresión

En Kotlin if no es solo un statement — es una expresión que retorna un valor. Esto elimina la necesidad del operador ternario ? : de Java:

// if como statement (igual que Java)
if (edad >= 18) {
    println("Mayor de edad")
} else {
    println("Menor de edad")
}

// if como expresión — retorna un valor
val mensaje = if (edad >= 18) "Mayor de edad" else "Menor de edad"

// Con bloques — el valor retornado es la última expresión del bloque
val categoria = if (puntos > 1000) {
    println("Calculando categoría premium...")
    "Premium"
} else if (puntos > 500) {
    "Gold"
} else {
    "Standard"
}

// Reemplaza el ternario de Java:
// Java:  String msg = edad >= 18 ? "Mayor" : "Menor";
// Kotlin: val msg = if (edad >= 18) "Mayor" else "Menor"

when — el switch mejorado

when reemplaza al switch de Java pero es mucho más poderoso. También es una expresión:

// when básico — como switch
val dia = 3
val nombreDia = when (dia) {
    1 -> "Lunes"
    2 -> "Martes"
    3 -> "Miércoles"
    4 -> "Jueves"
    5 -> "Viernes"
    6, 7 -> "Fin de semana"   // múltiples valores
    else -> "Día inválido"
}

// when con rangos y condiciones
val descripcion = when (edad) {
    in 0..12   -> "Niño"
    in 13..17  -> "Adolescente"
    in 18..64  -> "Adulto"
    else       -> "Adulto mayor"
}

// when sin argumento — reemplaza cadenas de if-else
val resultado = when {
    temperatura < 0   -> "Bajo cero"
    temperatura < 20  -> "Frío"
    temperatura < 30  -> "Agradable"
    else              -> "Calor"
}

// when con type checking — muy poderoso con sealed classes
sealed class Forma
data class Circulo(val radio: Double) : Forma()
data class Rectangulo(val ancho: Double, val alto: Double) : Forma()
data class Triangulo(val base: Double, val altura: Double) : Forma()

fun area(forma: Forma): Double = when (forma) {
    is Circulo    -> Math.PI * forma.radio * forma.radio
    is Rectangulo -> forma.ancho * forma.alto
    is Triangulo  -> forma.base * forma.altura / 2
    // No necesita else — el compilador sabe que cubrís todos los casos
}

when exhaustivoCuando usás when como expresión con una sealed class, el compilador exige que cubras todos los casos. Si agregás un nuevo subtipo y olvidás manejarlo, el código no compila. Es la misma ventaja que mencionamos en la lección de StateFlow.

for y rangos

// Iterar sobre una colección
val nombres = listOf("Ana", "Carlos", "María")
for (nombre in nombres) {
    println(nombre)
}

// Con índice
for ((indice, nombre) in nombres.withIndex()) {
    println("$indice: $nombre")
}

// Rangos numéricos
for (i in 1..5) print("$i ")      // 1 2 3 4 5
for (i in 1 until 5) print("$i ") // 1 2 3 4 (sin incluir 5)
for (i in 5 downTo 1) print("$i ")// 5 4 3 2 1
for (i in 0..10 step 2) print("$i ")  // 0 2 4 6 8 10

// Iterar sobre un Map
val precios = mapOf("Tablet" to 299.0, "Phone" to 499.0)
for ((producto, precio) in precios) {
    println("$producto: $$precio")
}

// repeat — cuando solo necesitás repetir N veces
repeat(3) { println("Hola!") }

// forEach — estilo funcional
nombres.forEach { println(it) }
nombres.forEachIndexed { i, nombre -> println("$i: $nombre") }

while y do-while

// while — igual que en Java
var intentos = 0
while (intentos < 3) {
    val resultado = intentarConexion()
    if (resultado.exitoso) break
    intentos++
}

// do-while — ejecuta al menos una vez
do {
    val input = leerInput()
    procesarInput(input)
} while (input != "salir")

break y continue con labels

Kotlin permite etiquetar loops para controlar exactamente cuál romper o saltear en loops anidados:

// break en loop anidado — solo sale del loop interno
for (i in 1..3) {
    for (j in 1..3) {
        if (j == 2) break  // solo sale del for(j)
        println("$i,$j")
    }
}

// Con label — sale del loop externo
externo@ for (i in 1..3) {
    for (j in 1..3) {
        if (i == 2 && j == 2) break@externo  // sale del for(i)
        println("$i,$j")
    }
}

// continue con label — salta al siguiente del loop etiquetado
externo@ for (i in 1..3) {
    for (j in 1..3) {
        if (j == 2) continue@externo  // salta al siguiente i
        println("$i,$j")
    }
}

Smart casts

Después de un chequeo de tipo o null, Kotlin sabe automáticamente el tipo exacto — no necesitás castear manualmente:

fun procesar(valor: Any) {
    // is verifica el tipo
    if (valor is String) {
        // Acá el compilador sabe que valor es String — smart cast automático
        println(valor.uppercase())   // no necesitás (valor as String).uppercase()
        println(valor.length)
    }

    if (valor is Int) {
        println(valor * 2)  // Int smart cast
    }
}

// Smart cast con null check
fun mostrar(texto: String?) {
    if (texto == null) return
    // Acá el compilador sabe que texto es String (no nullable)
    println(texto.uppercase())  // sin ?. ni !!
}

// Smart cast en when
fun describir(obj: Any): String = when (obj) {
    is Int    -> "Entero: ${obj * 2}"    // obj es Int acá
    is String -> "Texto: ${obj.length} chars"  // obj es String acá
    is List<*> -> "Lista: ${obj.size} elementos"
    else      -> "Otro: $obj"
}