Iterasi adalah operasi yang dilakukan pada hampir setiap collection. Kotlin menyediakan banyak gaya iterasi: for-in tradisional, forEach fungsional, forEachIndexed dengan indeks, hingga iterator manual. Pada chapter ini kita akan membandingkan tiap gaya dan memahami protokol Iterator<T> yang menjadi dasar semuanya. Kita juga akan membuat iterator kustom yang menghasilkan deret Fibonacci sebagai contoh penerapan protokol tersebut.

Gaya Iterasi Umum #

BentukPenggunaanCocok untuk
for (x in coll)Loop tradisionalIterasi sederhana, bisa pakai break/continue
coll.forEach { }LambdaPipeline fungsional, tidak ada break
coll.forEachIndexed { i, x -> }Lambda dengan indeksPerlu tahu posisi elemen
for (i in coll.indices)IndeksAkses coll[i] eksplisit
for ((i, x) in coll.withIndex())IndexedValueIndeks + nilai, ringkas
for ((k, v) in map)DestructuringIterasi Map.Entry

For-in dan ForEach #

for (x in coll) adalah sintaks loop tradisional. Di balik layar, kompilator mengubahnya menjadi coll.iterator() lalu while (iter.hasNext()) { val x = iter.next() }. Loop for-in mendukung break dan continue.

forEach { } adalah function higher-order yang menerima lambda (T) -> Unit. Lambda dipanggil sekali per elemen. Karena forEach adalah function biasa (bukan loop), break dan continue tidak bisa dipakai — kecuali dengan labeled return dari dalam lambda.

withIndex dan indices #

Untuk mendapatkan indeks bersamaan dengan nilai, ada dua pilihan:

  • for (i in coll.indices) lalu akses coll[i] — iterasi indeks.
  • for ((i, x) in coll.withIndex()) — deklarasi destructuring dari IndexedValue.

withIndex lebih ringkas dan menghindari pemanggilan coll[i] berulang yang bisa berbiaya pada struktur akses lambat.

Iterator Protocol #

Setiap Iterable<T> memiliki metode iterator() yang mengembalikan Iterator<T>. Iterator<T> adalah interface dengan dua metode:

kotlin Contoh: interface Iterator
interface Iterator<out T> {
    operator fun hasNext(): Boolean
    operator fun next(): T
}

Kita bisa membuat iterator kustom untuk menghasilkan deret nilai secara lazy. Contoh klasik adalah deret Fibonacci yang dihitung on-demand:

kotlin Contoh: Iterator Fibonacci
class FibIterator : Iterator<Int> {
    var a = 0
    var b = 1
    override fun hasNext() = a < 100
    override fun next(): Int {
        val cur = a
        val next = a + b
        a = b; b = next
        return cur
    }
}

Iterator ini tidak menyimpan seluruh deret Fibonacci di memori — setiap elemen dihitung saat next() dipanggil. Inilah inti evaluasi lazy yang akan kita jumpai lagi pada chapter Sequences.

Contoh Lengkap #

kotlin
fun main() {
    val buah = listOf("Apel", "Jeruk", "Pisang", "Mangga")

    // for loop dengan in
    print("For-each: ")
    for (b in buah) {
        print("$b ")
    }
    println()

    // forEach dengan lambda
    print("forEach: ")
    buah.forEach { print("$it ") }
    println()

    // forEachIndexed
    println("forEachIndexed:")
    buah.forEachIndexed { i, b -> println("  [$i] $b") }

    // for dengan indeks: indices
    println("Dengan indices:")
    for (i in buah.indices) {
        println("  $i -> ${buah[i]}")
    }

    // withIndex: indeks + nilai
    println("withIndex:")
    for ((i, b) in buah.withIndex()) {
        println("  $i: $b")
    }

    // Iterasi Map
    val peta = mapOf("Jepang" to "Tokyo", "Korea" to "Seoul")
    println("Iterasi Map entries:")
    for ((k, v) in peta) {
        println("  $k -> $v")
    }

    // Iterasi String (CharSequence)
    val teks = "Kotlin"
    print("Iterasi String: ")
    for (ch in teks) {
        print("$ch ")
    }
    println()

    // Iterator manual: hasNekt / next
    println("Iterator manual:")
    val iter = buah.iterator()
    while (iter.hasNext()) {
        println("  ${iter.next()}")
    }

    // Iterator kustom: deret Fibonacci terbatas
    class FibIterator : Iterator<Int> {
        var a = 0; var b = 1
        override fun hasNext() = a < 100
        override fun next(): Int {
            val cur = a
            val next = a + b
            a = b; b = next
            return cur
        }
    }

    val fibSeq = object : Iterable<Int> {
        override fun iterator() = FibIterator()
    }
    print("Fibonacci < 100: ")
    for (n in fibSeq) print("$n ")
    println()

    // ListIterator: bisa maju dan mundur
    val li = buah.listIterator()
    println("Maju:")
    while (li.hasNext()) println("  ${li.nextIndex()}: ${li.next()}")
    println("Mundur:")
    while (li.hasPrevious()) println("  ${li.previousIndex()}: ${li.previous()}")

    // Iterasi range dengan step
    print("Range step 3: ")
    for (i in 0..10 step 3) print("$i ")
    println()
}

Output yang Dihasilkan:

text
For-each: Apel Jeruk Pisang Mangga 
forEach: Apel Jeruk Pisang Mangga 
forEachIndexed:
  [0] Apel
  [1] Jeruk
  [2] Pisang
  [3] Mangga
Dengan indices:
  0 -> Apel
  1 -> Jeruk
  2 -> Pisang
  3 -> Mangga
withIndex:
  0: Apel
  1: Jeruk
  2: Pisang
  3: Mangga
Iterasi Map entries:
  Jepang -> Tokyo
  Korea -> Seoul
Iterasi String: K o t l i n 
Iterator manual:
  Apel
  Jeruk
  Pisang
  Mangga
Fibonacci < 100: 0 1 1 2 3 5 8 13 21 34 55 89 
Maju:
  0: Apel
  1: Jeruk
  2: Pisang
  3: Mangga
Mundur:
  3: Mangga
  2: Pisang
  1: Jeruk
  0: Apel
Range step 3: 0 3 6 9

Mari kita bedah baris-baris penting di atas:

  1. for (b in buah): kompilator mengubah loop ini menjadi val iter = buah.iterator(); while (iter.hasNext()) { val b = iter.next(); ... }. Inilah mengapa for-in bekerja pada setiap Iterable.
  2. buah.forEach { print("$it ") }: lambda dipanggil sekali per elemen. it adalah nama implisit untuk parameter tunggal. Karena forEach adalah function, kita tidak bisa menggunakan break — kecuali dengan return@forEach berlabel.
  3. buah.indices: mengembalikan IntRange dari 0 hingga size - 1. Iterasi for (i in buah.indices) memberi kita setiap indeks untuk akses buah[i].
  4. for ((i, b) in buah.withIndex()): withIndex() mengembalikan Iterable<IndexedValue<T>>. IndexedValue memiliki index dan value. Destructuring mengeluarkan keduanya menjadi i dan b.
  5. for ((k, v) in peta): iterasi pada Map menghasilkan Map.Entry. Destructuring mengeluarkan key dan value. Implementasi default LinkedHashMap mempertahankan urutan penyisipan.
  6. for (ch in teks): String mengimplementasikan CharSequence yang Iterable<Char>. Tiap iterasi menghasilkan satu Char.
  7. val iter = buah.iterator(); while (iter.hasNext()) { iter.next() }: ini adalah bentuk manual yang setara dengan for (b in buah). Jarang ditulis tangan kecuali untuk algoritma khusus yang memerlukan kontrol iterator eksplisit.
  8. FibIterator: hasNext() mengembalikan true selama a < 100. next() mengembalikan nilai Fibonacci saat ini dan menggeser state. Tidak ada batas atas tetap — iterator menghitung nilai berikutnya saat diminta.
  9. object : Iterable<Int> { override fun iterator() = FibIterator() }: anonymous object yang mengimplementasi Iterable. Setiap pemanggilan iterator() membuat instance baru agar iterasi bisa diulang.
  10. li.hasPrevious() / li.previous(): ListIterator adalah iterator dua arah yang bisa maju dan mundur. nextIndex() dan previousIndex() memberi indeks tanpa menggerakkan kursor.

Referensi Lanjutan #

Setelah memahami iterasi eager, kita akan melihat evaluasi lazy melalui Sequence yang menghindari alokasi list antara pada pipeline operasi.