Pola when sering kali butuh dua pemeriksaan sekaligus: tipe (is String) dan kondisi tambahan (length > 5). Sebelum Kotlin 2.1, kita menulis pola ini dengan if bersarang di dalam branch, yang membuat kode tidak rapi. Guard conditions — sintaks is String && x.length > 5 di branch when — menyederhanakan pola tersebut menjadi satu baris. Fitur ini distabilkan di Kotlin 2.1; kompilator 2.0.21 yang dipakai di buku ini belum mendukungnya tanpa flag eksperimental. Pada chapter ini kita akan melihat sintaks resmi guard, menulis pola manual setara yang kompatibel dengan 2.0.21, dan membahas kapan guard benar-benar menghasilkan kode yang lebih baik.

Sintaks Guard Conditions #

Sintaks resmi di Kotlin 2.1+ menggabungkan smart cast (is Type) dengan kondisi boolean tambahan:

kotlin Guard conditions (Kotlin 2.1+)
fun klasifikasi(x: Any): String = when (x) {
    is String && x.length > 5 -> "string panjang (${x.length})"
    is String                  -> "string pendek (${x.length})"
    is Int && x > 0            -> "bilangan bulat positif ($x)"
    is Int                     -> "bilangan nol atau negatif ($x)"
    else                       -> "tipe lain"
}

Tanda && setelah is Type berperan sebagai guard — kondisi tambahan yang harus terpenuhi agar branch dipilih. Smart cast tetap berlaku setelah is Type, jadi x.length dan x > 0 bisa dipanggil tanpa cast eksplisit.

Pola Manual Setara (Kompatibel 2.0.21) #

Karena kompilator 2.0.21 belum mendukung guard conditions secara stabil, kita menulis dua pendekatan manual yang menghasilkan perilaku sama:

kotlin
// guard_conditions.kt
//
// Mendemonstrasikan konsep "guard conditions" pada `when` (Kotlin 2.1+):
//   when (x) {
//       is String && x.length > 5 -> "string panjang"
//       is String -> "string pendek"
//   }
//
// Fitur ini belum stabil di kotlinc 2.0.21 (perlu flag
// `-XXLanguage:+WhenGuards` atau compiler Kotlin 2.1+).
// File ini mendemonstrasikan dua hal:
//   (a) Ekspresi manual yang setara dengan guard (kompatibel 2.0.21)
//   (b) Pola idiomatic sebelum guard tersedia
//
// Lihat index.md untuk sintaks guard sesungguhnya.

// 1. Versi manual dengan when + if-else (kompatibel 2.0.21)
fun klasifikasiManual(x: Any): String {
    return when (x) {
        is String -> if (x.length > 5) "string panjang (${x.length})"
                      else "string pendek (${x.length})"
        is Int -> if (x > 0) "bilangan bulat positif ($x)"
                   else if (x < 0) "bilangan bulat negatif ($x)"
                   else "bilangan nol"
        is List<*> -> if (x.size > 3) "list besar (${x.size})"
                       else "list kecil (${x.size})"
        else -> "tipe lain"
    }
}

// 2. Versi equivalen dengan if-else-if (lebih verbose)
fun klasifikasiIf(x: Any): String {
    if (x is String && x.length > 5) return "string panjang (${x.length})"
    if (x is String) return "string pendek (${x.length})"
    if (x is Int && x > 0) return "bilangan bulat positif ($x)"
    if (x is Int && x < 0) return "bilangan bulat negatif ($x)"
    if (x is Int) return "bilangan nol"
    if (x is List<*> && x.size > 3) return "list besar (${x.size})"
    if (x is List<*>) return "list kecil (${x.size})"
    return "tipe lain"
}

// 3. Mengapa guard bermanfaat: kombinasi `is Type` + kondisi boolean tambahan
//    Tanpa guard, kita harus menulis ulang smart cast atau menggunakan `when(x)` lalu `if` di dalam.
sealed interface Event {
    data class Klik(val jumlah: Int) : Event
    data class Ketik(val teks: String) : Event
    data object Tutup : Event
}

fun tanganiEventManual(e: Event): String {
    return when (e) {
        is Event.Klik -> if (e.jumlah > 1) "klik ganda (${e.jumlah})"
                         else "klik tunggal"
        is Event.Ketik -> if (e.teks.length > 20) "teks panjang"
                           else "teks pendek"
        Event.Tutup -> "tutup aplikasi"
    }
}

fun main() {
    println("=== Guard Conditions (Konsep) ===")
    println("Fitur `when` guard tersedia di Kotlin 2.1+")
    println("Pada 2.0.21, gunakan ekspresi manual setara.")
    println()

    println("--- 1. Versi manual dengan when + if-else ---")
    val data: List<Any> = listOf(
        "halo",
        "halo dunia kotlin",
        42,
        -7,
        0,
        listOf(1, 2, 3),
        listOf(1, 2, 3, 4, 5),
        true
    )
    data.forEach { println("  $it -> ${klasifikasiManual(it)}") }

    println()
    println("--- 2. Versi if-else-if berantai ---")
    data.forEach { println("  $it -> ${klasifikasiIf(it)}") }

    println()
    println("--- 3. Penerapan pada sealed class ---")
    val event: List<Event> = listOf(
        Event.Klik(1),
        Event.Klik(3),
        Event.Ketik("halo"),
        Event.Ketik("ini adalah teks yang sangat panjang dan kaya"),
        Event.Tutup
    )
    event.forEach { println("  $it -> ${tanganiEventManual(it)}") }

    println()
    println("--- 4. Perbandingan sintaks: dengan vs tanpa guard ---")
    println("TANPA guard (2.0.21):")
    println("    when (x) {")
    println("        is String -> if (x.length > 5) \"panjang\" else \"pendek\"")
    println("    }")
    println()
    println("DENGAN guard (2.1+):")
    println("    when (x) {")
    println("        is String && x.length > 5 -> \"panjang\"")
    println("        is String                  -> \"pendek\"")
    println("    }")

    println()
    println("--- 5. Aturan pakai guard ---")
    val aturan = listOf(
        "Guard muncul SETELAH `is Type` atau `in Range`",
        "Dipisahkan dengan `&&`",
        "Bisa ada beberapa guard di satu branch",
        "Smart cast tetap berlaku setelah `is Type`"
    )
    aturan.forEach { println("  - $it") }
}

Output yang Dihasilkan:

text
=== Guard Conditions (Konsep) ===
Fitur `when` guard tersedia di Kotlin 2.1+
Pada 2.0.21, gunakan ekspresi manual setara.

--- 1. Versi manual dengan when + if-else ---
  halo -> string pendek (4)
  halo dunia kotlin -> string panjang (17)
  42 -> bilangan bulat positif (42)
  -7 -> bilangan bulat negatif (-7)
  0 -> bilangan nol
  [1, 2, 3] -> list kecil (3)
  [1, 2, 3, 4, 5] -> list besar (5)
  true -> tipe lain

--- 2. Versi if-else-if berantai ---
  halo -> string pendek (4)
  halo dunia kotlin -> string panjang (17)
  42 -> bilangan bulat positif (42)
  -7 -> bilangan bulat negatif (-7)
  0 -> bilangan nol
  [1, 2, 3] -> list kecil (3)
  [1, 2, 3, 4, 5] -> list besar (5)
  true -> tipe lain

--- 3. Penerapan pada sealed class ---
  Klik(jumlah=1) -> klik tunggal
  Klik(jumlah=3) -> klik ganda (3)
  Ketik(teks=halo) -> teks pendek
  Ketik(teks=ini adalah teks yang sangat panjang dan kaya) -> teks panjang
  Tutup -> tutup aplikasi

--- 4. Perbandingan sintaks: dengan vs tanpa guard ---
TANPA guard (2.0.21):
    when (x) {
        is String -> if (x.length > 5) "panjang" else "pendek"
    }

DENGAN guard (2.1+):
    when (x) {
        is String && x.length > 5 -> "panjang"
        is String                  -> "pendek"
    }

--- 5. Aturan pakai guard ---
  - Guard muncul SETELAH `is Type` atau `in Range`
  - Dipisahkan dengan `&&`
  - Bisa ada beberapa guard di satu branch
  - Smart cast tetap berlaku setelah `is Type`

Pada versi pertama, kita mempertahankan struktur when (yang mempermudah smart cast dan exhaustiveness check) tetapi menambahkan if-else di dalam branch. Pada versi kedua, kita memakai if-else-if berantai yang lebih ringkas untuk kasus sederhana, tetapi kehilangan exhaustiveness check otomatis dari kompilator. Guard conditions di Kotlin 2.1+ menggabungkan kelebihan kedua pendekatan — when tetap rapi dan smart cast tetap bekerja.

Penerapan pada Sealed Class #

Salah satu pemicu utama fitur ini adalah pengolahan sealed class dengan banyak variant yang punya kondisi tambahan:

kotlin Guard untuk sealed class event
sealed interface Event {
    data class Klik(val jumlah: Int) : Event
    data class Ketik(val teks: String) : Event
    data object Tutup : Event
}

// Kotlin 2.1+: guard conditions
fun tangani(e: Event): String = when (e) {
    is Event.Klik && e.jumlah > 1 -> "klik ganda (${e.jumlah})"
    is Event.Klik                 -> "klik tunggal"
    is Event.Ketik && e.teks.length > 20 -> "teks panjang"
    is Event.Ketik                        -> "teks pendek"
    Event.Tutup                          -> "tutup aplikasi"
}

Sebelum guard conditions, kita harus menulis if-else bersarang di setiap branch when. Dengan guard, kode menjadi lebih deklaratif — setiap branch menyatakan dengan jelas kondisi yang harus terpenuhi.

Aturan Pakai Guard #

Beberapa aturan penting saat menulis guard conditions:

  1. Guard muncul setelah is Type atau in Range — tidak boleh ada guard tanpa kondisi tipe atau range di depannya.
  2. Dipisahkan dengan && — bukan ||. Kondisi gabungan dengan || tetap butuh branch terpisah.
  3. Smart cast berlaku setelah is Type — bahkan sebelum guard dievaluasi, sehingga x.length aman dipanggil.
  4. Urutan branch tetap penting — branch pertama yang cocok akan dipilih. Letakkan branch yang lebih spesifik (dengan guard) sebelum branch yang lebih umum.

Kapan Guard Tidak Membantu #

Guard conditions bukan solusi universal. Hindari guard untuk kasus berikut:

  • Kondisi sederhana yang hanya butuh is Type tanpa pemeriksaan tambahan — tidak perlu guard.
  • Kondisi kompleks yang butuh lebih dari dua pemeriksaan — lebih baik ekstrak ke fungsi terpisah agar branch when tetap pendek.
  • Pemeriksaan yang melibatkan efek samping (mis. logging) — guard seharusnya murni kondisional tanpa side effect.

Referensi Lanjutan #

Selanjutnya #

Guard conditions adalah salah satu bagian dari when modern. Chapter berikutnya membahas when tingkat lanjut lebih luas — destructuring, binding variabel subject, range, dan multiple values — yang sudah stabil di Kotlin 2.0.21.