Perulangan

Perulangan #

Perulangan adalah mekanisme untuk mengeksekusi blok kode secara berulang — baik sejumlah kali yang sudah ditentukan, maupun selama kondisi tertentu terpenuhi. Kotlin menyediakan tiga kata kunci perulangan klasik: for, while, dan do-while. Tapi Kotlin juga mendorong pendekatan yang lebih deklaratif melalui fungsi higher-order seperti forEach, map, filter, dan reduce — yang sering kali lebih ekspresif dan lebih aman dari kesalahan umum loop manual seperti off-by-one error. Artikel ini membahas semua bentuk perulangan di Kotlin, kapan menggunakan masing-masing, dan pola-pola yang membuat kode perulangan lebih bersih.

Perulangan for #

for di Kotlin mengiterasi apa pun yang mengimplementasikan Iterable — range, array, list, map, string, dan lainnya. Tidak ada bentuk for (i = 0; i < n; i++) seperti di Java atau C — Kotlin menggantikannya dengan sintaks yang lebih deklaratif.

Iterasi Range #

// Inklusif di kedua ujung: 1, 2, 3, 4, 5
for (i in 1..5) {
    print("$i ")
}
// 1 2 3 4 5

// Eksklusif di ujung kanan: 0, 1, 2, 3, 4
for (i in 0 until 5) {
    print("$i ")
}
// 0 1 2 3 4

// Mundur: 5, 4, 3, 2, 1
for (i in 5 downTo 1) {
    print("$i ")
}
// 5 4 3 2 1

// Dengan langkah: 0, 2, 4, 6, 8, 10
for (i in 0..10 step 2) {
    print("$i ")
}
// 0 2 4 6 8 10

// Mundur dengan langkah: 10, 7, 4, 1
for (i in 10 downTo 1 step 3) {
    print("$i ")
}
// 10 7 4 1

Iterasi Koleksi #

val bahasa = listOf("Kotlin", "Java", "Python", "Go")

// Iterasi elemen
for (lang in bahasa) {
    println(lang)
}

// Iterasi dengan indeks menggunakan withIndex()
for ((indeks, lang) in bahasa.withIndex()) {
    println("[$indeks] $lang")
}
// [0] Kotlin
// [1] Java
// [2] Python
// [3] Go

// Iterasi hanya indeks jika elemen tidak dibutuhkan
for (i in bahasa.indices) {
    println("Posisi $i: ${bahasa[i]}")
}

Iterasi Map #

val ibuKota = mapOf(
    "Indonesia" to "Jakarta",
    "Jepang"    to "Tokyo",
    "Prancis"   to "Paris",
    "Brazil"    to "Brasilia"
)

// Destructuring entry map langsung di for
for ((negara, kota) in ibuKota) {
    println("$negara$kota")
}

// Jika hanya butuh key atau value
for (negara in ibuKota.keys) print("$negara ")
for (kota in ibuKota.values) print("$kota ")

Iterasi String #

String juga bisa diiterasi karakter per karakter:

val kata = "Kotlin"

for (karakter in kata) {
    print("$karakter-")
}
// K-o-t-l-i-n-

// Dengan indeks
for ((i, c) in kata.withIndex()) {
    println("[$i] = '$c'")
}

Perulangan while #

while mengevaluasi kondisi sebelum setiap iterasi. Jika kondisi langsung false sejak awal, blok kode tidak pernah dieksekusi sama sekali.

var hitungan = 1

while (hitungan <= 5) {
    println("Iterasi ke-$hitungan")
    hitungan++
}

while paling tepat digunakan ketika jumlah iterasi tidak diketahui sebelumnya dan bergantung pada kondisi dinamis:

// Simulasi percobaan koneksi
var percobaan = 0
val maksPercobaan = 3
var terhubung = false

while (!terhubung && percobaan < maksPercobaan) {
    percobaan++
    println("Percobaan koneksi ke-$percobaan...")
    terhubung = cobaSambungkan()  // fungsi yang mengembalikan Boolean

    if (!terhubung && percobaan < maksPercobaan) {
        println("Gagal, mencoba lagi dalam 2 detik...")
        Thread.sleep(2_000)
    }
}

if (terhubung) {
    println("Berhasil terhubung!")
} else {
    println("Koneksi gagal setelah $maksPercobaan percobaan.")
}

Infinite Loop dengan while #

// Loop selamanya — harus ada mekanisme keluar (break atau return)
while (true) {
    val input = bacaInput()
    if (input == "keluar") break
    prosesInput(input)
}

Perulangan do-while #

do-while mengevaluasi kondisi setelah setiap iterasi. Ini menjamin blok kode dieksekusi minimal satu kali, bahkan jika kondisi sudah false sejak awal.

var i = 10

do {
    println("Nilai i: $i")
    i++
} while (i <= 5)
// Output: "Nilai i: 10" — dieksekusi sekali meski kondisi langsung false

Use case paling klasik do-while adalah meminta input pengguna sampai input valid:

var input: String
var angka: Int

do {
    print("Masukkan angka antara 1 dan 10: ")
    input = readLine() ?: ""
    angka = input.toIntOrNull() ?: -1

    if (angka !in 1..10) {
        println("Input tidak valid. Coba lagi.")
    }
} while (angka !in 1..10)

println("Kamu memasukkan: $angka")

Pola ini lebih alami dari while karena kamu tidak perlu menginisialisasi variabel dengan nilai dummy sebelum loop hanya untuk membuat kondisi pertama bisa dievaluasi.


break dan continue #

break — Keluar dari Loop #

break menghentikan perulangan sepenuhnya dan melanjutkan eksekusi ke kode setelah loop:

val daftar = listOf(3, 7, 2, 9, 1, 5, 8, 4)

var target = 9
var posisi = -1

for ((indeks, nilai) in daftar.withIndex()) {
    if (nilai == target) {
        posisi = indeks
        break  // tidak perlu lanjut setelah ketemu
    }
}

if (posisi >= 0) {
    println("$target ditemukan di indeks $posisi")
} else {
    println("$target tidak ditemukan")
}

continue — Lewati Iterasi Ini #

continue melewati sisa kode di iterasi saat ini dan langsung ke iterasi berikutnya:

val angka = listOf(1, -3, 5, -2, 8, -7, 4)

print("Angka positif: ")
for (n in angka) {
    if (n < 0) continue  // lewati angka negatif
    print("$n ")
}
// Angka positif: 1 5 8 4

Label — break dan continue pada Loop Bersarang #

Secara default, break dan continue hanya mempengaruhi loop paling dalam. Untuk mempengaruhi loop luar, gunakan label.

// ANTI-PATTERN: mencoba keluar dari loop luar dengan flag boolean
var ditemukan = false
for (i in 1..5) {
    for (j in 1..5) {
        if (i * j == 12) {
            ditemukan = true
            break  // hanya keluar dari loop dalam!
        }
    }
    if (ditemukan) break  // perlu break kedua untuk loop luar
}

// BENAR: gunakan label untuk langsung keluar dari loop luar
luarLoop@ for (i in 1..5) {
    for (j in 1..5) {
        if (i * j == 12) {
            println("Ditemukan: $i × $j = 12")
            break@luarLoop  // langsung keluar dari loop berlabel
        }
    }
}
// Ditemukan: 3 × 4 = 12

Label juga bekerja dengan continue:

luarLoop@ for (i in 1..3) {
    for (j in 1..3) {
        if (j == 2) continue@luarLoop  // lanjut ke iterasi berikutnya dari loop luar
        println("i=$i, j=$j")
    }
}
// i=1, j=1
// i=2, j=1
// i=3, j=1

repeat — Perulangan Sederhana Sejumlah Kali #

Untuk mengulang sesuatu sejumlah N kali tanpa butuh variabel counter, repeat adalah pilihan paling bersih:

// ANTI-PATTERN: for hanya untuk menghitung
for (i in 1..5) {
    println("Halo!")
}

// BENAR: repeat jika tidak butuh nilai counter
repeat(5) {
    println("Halo!")
}

// repeat menyediakan index jika dibutuhkan
repeat(5) { indeks ->
    println("Iterasi ke-$indeks")
}
// Iterasi ke-0
// Iterasi ke-1
// ...
// Iterasi ke-4

Perulangan Bersarang #

Loop di dalam loop berguna untuk bekerja dengan struktur data dua dimensi seperti matriks, tabel, atau kombinasi.

// Tabel perkalian
for (i in 1..5) {
    for (j in 1..5) {
        print("%4d".format(i * j))
    }
    println()
}
//    1   2   3   4   5
//    2   4   6   8  10
//    3   6   9  12  15
//    4   8  12  16  20
//    5  10  15  20  25
// Mencari pasangan elemen yang jumlahnya sama dengan target
val angka = listOf(1, 3, 5, 7, 9)
val target = 10

for (i in angka.indices) {
    for (j in i + 1 until angka.size) {
        if (angka[i] + angka[j] == target) {
            println("${angka[i]} + ${angka[j]} = $target")
        }
    }
}
// 1 + 9 = 10
// 3 + 7 = 10
Loop bersarang lebih dari dua tingkat biasanya pertanda kode perlu di-refactor — ekstrak loop dalam ke fungsi tersendiri, atau pertimbangkan pendekatan yang berbeda. Kompleksitas bersarang membuat kode sulit dibaca dan dipahami.

Perulangan Fungsional — Alternatif for yang Lebih Ekspresif #

Kotlin mendorong penggunaan fungsi higher-order sebagai alternatif loop manual untuk operasi umum pada koleksi. Pendekatan fungsional biasanya lebih ekspresif, lebih aman dari off-by-one error, dan lebih mudah dikombinasikan.

flowchart TD
    A[Kebutuhan Perulangan] --> B{Tujuan?}
    B -- Lakukan sesuatu\ntiap elemen --> C["forEach { }"]
    B -- Transformasi\ntiap elemen --> D["map { }"]
    B -- Saring elemen --> E["filter { }"]
    B -- Akumulasi\nmenjadi satu nilai --> F["reduce { } / fold { }"]
    B -- Cari satu elemen --> G["find { } / first { }"]
    B -- Cek kondisi\nseluruh elemen --> H["all { } / any { } / none { }"]
    B -- Perlu index\ndi tiap iterasi --> I["forEachIndexed { }"]
    B -- Loop N kali\ntanpa koleksi --> J["repeat(N) { }"]
    B -- Kondisi dinamis\ntidak diketahui --> K["while / do-while"]

forEach dan forEachIndexed #

val mahasiswa = listOf("Budi", "Sari", "Ahmad", "Rina")

// forEach — tanpa indeks
mahasiswa.forEach { nama ->
    println("Halo, $nama!")
}

// forEachIndexed — dengan indeks
mahasiswa.forEachIndexed { i, nama ->
    println("${i + 1}. $nama")
}
// 1. Budi
// 2. Sari
// 3. Ahmad
// 4. Rina

map — Transformasi Setiap Elemen #

val harga = listOf(50_000, 75_000, 120_000, 30_000)

// ANTI-PATTERN: loop manual untuk transformasi
val hargaDiskon = mutableListOf<Int>()
for (h in harga) {
    hargaDiskon.add((h * 0.8).toInt())
}

// BENAR: map lebih ringkas dan aman
val hargaDiskon = harga.map { (it * 0.8).toInt() }

println(hargaDiskon)  // [40000, 60000, 96000, 24000]

filter — Menyaring Elemen #

val nilai = listOf(55, 78, 90, 42, 85, 67, 91, 38)

// Loop manual
val lulus = mutableListOf<Int>()
for (n in nilai) {
    if (n >= 70) lulus.add(n)
}

// Idiomatis
val lulus = nilai.filter { it >= 70 }
println(lulus)  // [78, 90, 85, 91]

Kombinasi Operasi — Method Chaining #

Kekuatan nyata pendekatan fungsional adalah kemudahan menggabungkan operasi:

data class Produk(val nama: String, val harga: Int, val stok: Int)

val produk = listOf(
    Produk("Laptop",   15_000_000, 5),
    Produk("Mouse",       250_000, 0),
    Produk("Keyboard",    500_000, 12),
    Produk("Monitor",   4_000_000, 3),
    Produk("Headset",     800_000, 0),
)

// Tampilkan produk tersedia, urutkan dari termurah, tampilkan nama dan harga
val tersedia = produk
    .filter { it.stok > 0 }
    .sortedBy { it.harga }
    .map { "${it.nama}: Rp${"%,d".format(it.harga)}" }

tersedia.forEach { println(it) }
// Keyboard: Rp500,000
// Monitor: Rp4,000,000
// Laptop: Rp15,000,000

Bandingkan dengan loop manual yang setara — jauh lebih banyak kode dan lebih rawan bug:

// ANTI-PATTERN: loop manual untuk hal yang sama
val tersedia = mutableListOf<Produk>()
for (p in produk) {
    if (p.stok > 0) tersedia.add(p)
}
tersedia.sortBy { it.harga }
val hasil = mutableListOf<String>()
for (p in tersedia) {
    hasil.add("${p.nama}: Rp${"%,d".format(p.harga)}")
}
for (s in hasil) println(s)

reduce dan fold — Akumulasi #

val angka = listOf(1, 2, 3, 4, 5)

// reduce: akumulasi mulai dari elemen pertama
val jumlah = angka.reduce { akum, nilai -> akum + nilai }
println(jumlah)  // 15

val perkalian = angka.reduce { akum, nilai -> akum * nilai }
println(perkalian)  // 120

// fold: akumulasi dengan nilai awal
val jumlahDenganOffset = angka.fold(100) { akum, nilai -> akum + nilai }
println(jumlahDenganOffset)  // 115

// sumOf — khusus untuk penjumlahan (lebih ekspresif dari reduce)
val total = angka.sumOf { it }
println(total)  // 15

Memilih Jenis Perulangan #

SituasiPilihan Terbaik
Iterasi koleksi/range tanpa modifikasifor atau forEach
Transformasi setiap elemenmap
Menyaring elemenfilter
Akumulasi ke satu nilaireduce / fold / sumOf
Iterasi dengan indeksforEachIndexed atau for + withIndex()
Ulangi N kali tanpa counterrepeat(N)
Kondisi dinamis, jumlah iterasi tidak pastiwhile
Minimal satu eksekusi, lalu cek kondisido-while
Keluar lebih awal dari loop bersarangfor + label + break@label

Ringkasan #

  • for di Kotlin adalah for-each — tidak ada bentuk for (i=0; i<n; i++). Gunakan range (1..5, 0 until n), withIndex(), atau indices untuk keperluan yang sama.
  • while untuk kondisi dinamis — pilih while saat jumlah iterasi tidak diketahui sebelumnya dan bergantung pada kondisi yang berubah selama eksekusi.
  • do-while menjamin minimal satu eksekusi — berguna untuk pola “coba dulu, cek kemudian”, seperti meminta input pengguna sampai valid.
  • repeat(N) untuk pengulangan sejumlah kali — lebih bersih dari for (i in 1..N) ketika nilai counter tidak dibutuhkan.
  • Label untuk break/continue pada loop bersarangbreak@label atau continue@label mempengaruhi loop berlabel, bukan hanya loop paling dalam.
  • Prefer pendekatan fungsional untuk koleksiforEach, map, filter, reduce, dan kombinasinya lebih ekspresif, lebih aman, dan lebih mudah digabungkan daripada loop manual.
  • Hindari loop bersarang lebih dari dua tingkat — ekstrak ke fungsi atau pertimbangkan pendekatan berbeda. Tiga tingkat bersarang hampir selalu bisa disederhanakan.
  • map bukan untuk side effect — gunakan forEach jika tujuannya melakukan aksi (print, simpan ke DB). Gunakan map jika tujuannya menghasilkan koleksi baru dari transformasi.

← Sebelumnya: Seleksi Kondisi   Berikutnya: Fungsi →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact