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 #
| Situasi | Pilihan Terbaik |
|---|---|
| Iterasi koleksi/range tanpa modifikasi | for atau forEach |
| Transformasi setiap elemen | map |
| Menyaring elemen | filter |
| Akumulasi ke satu nilai | reduce / fold / sumOf |
| Iterasi dengan indeks | forEachIndexed atau for + withIndex() |
| Ulangi N kali tanpa counter | repeat(N) |
| Kondisi dinamis, jumlah iterasi tidak pasti | while |
| Minimal satu eksekusi, lalu cek kondisi | do-while |
| Keluar lebih awal dari loop bersarang | for + label + break@label |
Ringkasan #
fordi Kotlin adalah for-each — tidak ada bentukfor (i=0; i<n; i++). Gunakan range (1..5,0 until n),withIndex(), atauindicesuntuk keperluan yang sama.whileuntuk kondisi dinamis — pilihwhilesaat jumlah iterasi tidak diketahui sebelumnya dan bergantung pada kondisi yang berubah selama eksekusi.do-whilemenjamin minimal satu eksekusi — berguna untuk pola “coba dulu, cek kemudian”, seperti meminta input pengguna sampai valid.repeat(N)untuk pengulangan sejumlah kali — lebih bersih darifor (i in 1..N)ketika nilai counter tidak dibutuhkan.- Label untuk
break/continuepada loop bersarang —break@labelataucontinue@labelmempengaruhi loop berlabel, bukan hanya loop paling dalam.- Prefer pendekatan fungsional untuk koleksi —
forEach,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.
mapbukan untuk side effect — gunakanforEachjika tujuannya melakukan aksi (print, simpan ke DB). Gunakanmapjika tujuannya menghasilkan koleksi baru dari transformasi.