when în Kotlin

Controlul execuției Kotlin 1.0 (when (val x = ...): 1.3; gărzi if: 2.2)

Alege o ramură după valoarea unei expresii sau după prima condiție adevărată; înlocuiește switch din alte limbaje.

Sintaxă
when (valoare) {
    1 -> ...
    2, 3 -> ...
    in 4..9 -> ...
    is String -> ...
    else -> ...
}

when {
    conditie1 -> ...
    else -> ...
}

Ce face when

when compară o valoare cu mai multe ramuri, de sus în jos, și execută prima ramură care se potrivește. O ramură poate avea o valoare (1 ->), mai multe valori separate prin virgulă (6, 7 ->), un interval (in 1..4 ->), o verificare de tip (is String ->) sau o expresie. else prinde tot ce a rămas. Nu există „cădere” în ramura următoare, deci nu ai nevoie de break.

Fără valoare între paranteze, when { ... } verifică pe rând condiții Boolean, ca un lanț de if-else if. Ca și if, when este o expresie: val text = when (x) { ... }. Folosit ca expresie trebuie să fie exhaustiv: fie are else, fie acoperă toate cazurile unui enum, sealed sau Boolean.

Poți declara variabila direct în paranteze: when (val r = calcul()) { ... }. Din Kotlin 2.2, o ramură poate avea și o gardă: is Card if suma > 1000 -> ..., adică o condiție în plus după potrivire.

Ce returnează

Folosit ca expresie: valoarea ramurii alese.

Exemple

Exemplul 1: Ziua săptămânii

Ramuri cu o valoare, cu mai multe valori și else. when este folosit ca expresie.

fun numeZi(zi: Int): String = when (zi) {
    1 -> "luni"
    2 -> "marți"
    3 -> "miercuri"
    4 -> "joi"
    5 -> "vineri"
    6, 7 -> "weekend"
    else -> "zi invalidă"
}

fun main() {
    for (zi in listOf(1, 5, 7, 9)) {
        println("$zi: ${numeZi(zi)}")
    }
}

Rezultat

1: luni
5: vineri
7: weekend
9: zi invalidă

Exemplul 2: Intervale și when fără argument

in 0..17 verifică un interval. A doua funcție folosește when fără valoare, cu condiții întregi.

fun categorie(varsta: Int) = when (varsta) {
    in 0..6 -> "preșcolar"
    in 7..17 -> "elev"
    in 18..64 -> "adult"
    else -> "pensionar"
}

fun livrare(km: Int, greutate: Double) = when {
    km <= 5 && greutate < 2 -> "bicicletă"
    km <= 50 -> "mașină"
    else -> "curier național"
}

fun main() {
    println("4 ani: ${categorie(4)}, 15 ani: ${categorie(15)}, 70 ani: ${categorie(70)}")
    println("Iași, 3 km, 1 kg: ${livrare(3, 1.0)}")
    println("Iași-Pașcani, 75 km: ${livrare(75, 5.0)}")
}

Rezultat

4 ani: preșcolar, 15 ani: elev, 70 ani: pensionar
Iași, 3 km, 1 kg: bicicletă
Iași-Pașcani, 75 km: curier național

Exemplul 3: Tipuri, variabilă în when și gardă

is verifică tipul și face smart cast. Garda if suma > 1000 (Kotlin 2.2) adaugă o condiție la ramură.

sealed interface Plata
data class Card(val suma: Double) : Plata
data class Numerar(val suma: Double) : Plata
data object Voucher : Plata

fun descrie(p: Plata) = when (p) {
    is Card if p.suma > 1000 -> "card, sumă mare: cere PIN"
    is Card -> "card: ${p.suma} lei"
    is Numerar -> "numerar: ${p.suma} lei"
    Voucher -> "voucher"
}

fun main() {
    listOf(Card(1500.0), Card(80.0), Numerar(20.0), Voucher).forEach { println(descrie(it)) }

    when (val rest = 107 % 10) {
        0 -> println("număr rotund")
        else -> println("restul împărțirii la 10 este $rest")
    }
}

Rezultat

card, sumă mare: cere PIN
card: 80.0 lei
numerar: 20.0 lei
voucher
restul împărțirii la 10 este 7

Greșeli frecvente

  • when folosit ca expresie fără else pe un Int sau String: eroarea 'when' expression must be exhaustive.
  • Un else pus la un when care acoperă deja toate valorile unui enum sau sealed: compilatorul avertizează că ramura este inutilă. Fără else, compilatorul te anunță dacă adaugi un caz nou și uiți să-l tratezi.
  • Ordinea ramurilor: se alege prima potrivire. in 0..100 pus înainte de in 0..10 face a doua ramură inutilă.

Comenzi înrudite

  • if și else: Execută un bloc doar dacă o condiție este adevărată; în Kotlin if este și expresie, deci întoarce o valoare.
  • is, !is și smart cast: is verifică tipul unei valori; după verificare, compilatorul o tratează automat ca acel tip (smart cast).
  • sealed class și sealed interface: sealed declară o ierarhie închisă: toate subtipurile sunt cunoscute la compilare, deci when le poate verifica pe toate fără else.
  • enum class: enum class declară un tip cu un număr fix de valori numite (zilele săptămânii, stările unei comenzi).
  • Range-uri (.., ..<, until, downTo, step): Intervale de numere sau caractere: .. include capătul, ..< și until îl exclud, downTo numără invers, step sare.