Collection adalah struktur data yang menyimpan banyak nilai sekaligus. Kotlin memetakan collection ke sebuah hierarki interface yang berasal dari pustaka standar, sehingga setiap fungsi bisa beroperasi pada banyak jenis collection sekaligus. Pada chapter ini kita akan menelusuri hierarki tersebut, mulai dari akar Iterable<T> hingga cabang List, Set, dan Map. Kita juga akan membedakan interface read-only dengan interface mutable dan memahami implikasi pemilihannya terhadap keamanan kode.

Akar Hierarki: Iterable dan Collection #

Semua collection di Kotlin berakar pada interface Iterable<T>. Interface ini hanya menyatakan satu kemampuan: koleksi tersebut dapat diiterasi melalui iterator. Turunan langsungnya adalah Collection<T>, yang menambah operasi seperti size, contains, dan isEmpty. Karena Iterable dan Collection bersifat read-only, kedua interface ini tidak memiliki metode untuk menambah, mengubah, atau menghapus elemen.

Dari Collection<T> lahir dua cabang utama:

InterfaceKontrak UtamaContoh Konkret
List<T>Berurutan, menerima duplikat, akses indekslistOf(1, 2, 3)
Set<T>Tidak menerima duplikat, tanpa jaminan urutansetOf("a", "b")

Map<K, V> tidak mewarisi Collection<T> karena menyimpan pasangan key-value, bukan nilai tunggal. Meskipun demikian, Map tetap dianggap bagian dari koleksi pustaka standar Kotlin.

Read-Only vs Mutable #

Setiap interface collection memiliki pasangan mutable:

  • List<T> → MutableList<T> menambah add, removeAt, set.
  • Set<T> → MutableSet<T> menambah add, remove.
  • Map<K, V> → MutableMap<K, V> menambah put, remove, clear.

Pemisahan ini memungkinkan kita menyimpan referensi read-only ke collection yang sebenarnya mutable. Pola umum dalam API Kotlin adalah menerima parameter List<String> (read-only) tetapi memprosesnya dengan MutableList<String> di internal.

Contoh Kode Konkret #

Mari kita lihat perbedaan keempat collection dasar dalam satu program:

kotlin
fun main() {
    // List read-only dan mutable
    val namaHari: List<String> = listOf("Senin", "Selasa", "Rabu")
    val namaHariMutable: MutableList<String> = mutableListOf("Senin", "Selasa", "Rabu")
    println("List read-only: $namaHari")
    println("MutableList awal: $namaHariMutable")
    namaHariMutable.add("Kamis")
    println("MutableList setelah add: $namaHariMutable")

    // Set read-only dan mutable
    val kodeUnik: Set<Int> = setOf(101, 202, 303)
    val kodeUnikMutable: MutableSet<Int> = mutableSetOf(101, 202, 303)
    println("Set read-only: ${kodeUnik.sorted()}")
    kodeUnikMutable.add(404)
    println("MutableSet setelah add: ${kodeUnikMutable.sorted()}")

    // Map read-only dan mutable
    val ibukota: Map<String, String> = mapOf("Jepang" to "Tokyo", "Korea" to "Seoul")
    val ibukotaMutable: MutableMap<String, String> = mutableMapOf("Jepang" to "Tokyo")
    println("Map read-only: $ibukota")
    ibukotaMutable["Korea"] = "Seoul"
    println("MutableMap setelah put: $ibukotaMutable")

    // List read-only tetap bisa dibaca, tidak bisa diubah
    println("Jumlah hari read-only: ${namaHari.size}")
    println("Hari pertama: ${namaHari[0]}")

    // Tipe runtime dari listOf adalah ArrayList (mutable di JVM)
    println("Runtime type listOf: ${namaHari::class.simpleName}")
    println("Runtime type mutableListOf: ${namaHariMutable::class.simpleName}")
}

Output yang Dihasilkan:

text
List read-only: [Senin, Selasa, Rabu]
MutableList awal: [Senin, Selasa, Rabu]
MutableList setelah add: [Senin, Selasa, Rabu, Kamis]
Set read-only: [101, 202, 303]
MutableSet setelah add: [101, 202, 303, 404]
Map read-only: {Jepang=Tokyo, Korea=Seoul}
MutableMap setelah put: {Jepang=Tokyo, Korea=Seoul}
Jumlah hari read-only: 3
Hari pertama: Senin
Runtime type listOf: ArrayList
Runtime type mutableListOf: ArrayList

Mari kita bedah baris-baris penting program di atas:

  1. val namaHari: List<String>: deklarasi variabel read-only bertipe List<String>. Meskipun objek runtime adalah ArrayList, kompilator hanya mengizinkan operasi yang dideklarasikan pada List.
  2. namaHariMutable.add("Kamis"): metode add hanya tersedia pada MutableList. Pemanggilan serupa pada namaHari akan ditolak kompilator.
  3. kodeUnik.sorted(): Set tidak memiliki indeks dan tidak menjamin urutan, sehingga untuk hasil deterministik kita urutkan sebelum mencetak.
  4. ibukotaMutable["Korea"] = "Seoul": notasi subscript pada MutableMap setara dengan pemanggilan put("Korea", "Seoul").
  5. namaHari::class.simpleName: refleksi tipe runtime membuktikan bahwa listOf mengembalikan ArrayList di JVM, bukan tipe khusus immutable.

Strategi Memilih Interface #

Berikut pedoman praktis memilih interface:

  • Default gunakan read-only (List, Set, Map) untuk parameter fungsi dan properti publik.
  • Gunakan mutable (MutableList, MutableSet, MutableMap) hanya ketika kita perlu memodifikasi isi di tempat.
  • Jika fungsi perlu membangun list sementara, gunakan mutableListOf di dalam, lalu kembalikan sebagai List ke pemanggil.

Referensi Lanjutan #

Dengan pemahaman hierarki ini, kita siap membahas komponen pertama secara mendalam: List akan dibahas pada chapter berikutnya.