List read-only membatasi kita pada operasi baca. Begitu kebutuhan berubah menjadi menambah, mengganti, atau menghapus elemen, kita beralih ke MutableList<T>. Interface ini menambah metode mutasi pada List<T> sambil tetap menyediakan semua operasi baca. Pada chapter ini kita akan menjelajahi operasi mutasi utama, urutan eksekusinya, dan jebakan ketika list mutable dipakai di lingkungan multi-thread.

Membuat MutableList #

MutableList dibuat menggunakan mutableListOf(...). Tipe elemen disimpulkan dari argumen pertama.

kotlin Contoh: deklarasi MutableList
val antrian: MutableList<String> = mutableListOf("A", "B", "C")
val angka = mutableListOf(1, 2, 3)             // MutableList<Int>
val kosong = mutableListOf<Double>()            // MutableList<Double> kosong

Sejak list ini mutable, kita bebas menambah, menyisipkan, mengganti, dan menghapus elemen selama program berjalan.

Operasi Mutasi Dasar #

MetodeefekContoh
add(x)menambah elemen di akhirtugas.add("Olahraga")
add(i, x)menyisipkan elemen di indeks itugas.add(1, "Masak")
set(i, x)mengganti elemen di indeks itugas[2] = "Cuci Mobil"
remove(x)menghapus kemunculan pertama xtugas.remove("Baca Buku")
removeAt(i)menghapus elemen pada indeks itugas.removeAt(0)
addAll(c)menambah semua elemen collection laintugas.addAll(listOf(...))
clear()mengosongkan listtugas.clear()

Metode remove(x) mengembalikan Boolean — true bila elemen benar-benar dihapus, false bila elemen tidak ditemukan. removeAt(i) mengembalikan elemen yang dihapus; metode ini melempar IndexOutOfBoundsException jika indeks tidak valid.

Contoh Lengkap #

Mari kita rangkai semua operasi dalam satu program:

kotlin
fun main() {
    val tugas = mutableListOf("Belanja", "Cuci Motor", "Baca Buku")
    println("Awal: $tugas")

    // Menambah elemen
    tugas.add("Olahraga")
    println("Setelah add: $tugas")

    // Menambah di indeks tertentu
    tugas.add(1, "Masak")
    println("Setelah add(1, ...): $tugas")

    // Mengganti elemen
    tugas[2] = "Cuci Mobil"
    println("Setelah set indeks 2: $tugas")

    // Menghapus
    tugas.remove("Baca Buku")
    println("Setelah remove(\"Baca Buku\"): $tugas")

    tugas.removeAt(0)
    println("Setelah removeAt(0): $tugas")

    // Operasi batch
    tugas.addAll(listOf("Belajar Kotlin", "Tidur"))
    println("Setelah addAll: $tugas")

    // Iterasi dengan indeks
    println("Daftar tugas:")
    tugas.forEachIndexed { i, item -> println("  ${i + 1}. $item") }

    // Sublist (view)
    val duaPertama = tugas.subList(0, 2)
    println("SubList(0,2): $duaPertama")

    // Clear semua
    val sementara = mutableListOf("a", "b", "c")
    println("Sementara sebelum clear: $sementara")
    sementara.clear()
    println("Sementara setelah clear: $sementara, isEmpty=${sementara.isEmpty()}")

    // Catatan thread safety
    println("List mutable TIDAK aman untuk akses multi-thread tanpa sinkronisasi")
}

Output yang Dihasilkan:

text
Awal: [Belanja, Cuci Motor, Baca Buku]
Setelah add: [Belanja, Cuci Motor, Baca Buku, Olahraga]
Setelah add(1, ...): [Belanja, Masak, Cuci Motor, Baca Buku, Olahraga]
Setelah set indeks 2: [Belanja, Masak, Cuci Mobil, Baca Buku, Olahraga]
Setelah remove("Baca Buku"): [Belanja, Masak, Cuci Mobil, Olahraga]
Setelah removeAt(0): [Masak, Cuci Mobil, Olahraga]
Setelah addAll: [Masak, Cuci Mobil, Olahraga, Belajar Kotlin, Tidur]
Daftar tugas:
  1. Masak
  2. Cuci Mobil
  3. Olahraga
  4. Belajar Kotlin
  5. Tidur
SubList(0,2): [Masak, Cuci Mobil]
Sementara sebelum clear: [a, b, c]
Sementara setelah clear: [], isEmpty=true
List mutable TIDAK aman untuk akses multi-thread tanpa sinkronisasi

Mari kita bedah baris-baris penting di atas:

  1. tugas.add(1, "Masak"): elemen "Masak" disisipkan pada indeks 1; elemen yang sebelumnya berada di indeks 1 dan setelahnya bergeser ke kanan.
  2. tugas[2] = "Cuci Mobil": subscript di sisi penugasan menjalankan set(2, "Cuci Mobil"), mengganti elemen pada indeks 2.
  3. tugas.remove("Baca Buku"): pencarian linear dimulai dari indeks 0. Hanya kemunculan pertama yang dihapus; sisanya tetap utuh bila ada.
  4. tugas.subList(0, 2): mengembalikan view — bukan salinan. Perubahan pada sublist akan terlihat pada list asli dan sebaliknya.
  5. sementara.clear(): menghapus semua elemen dan mengatur size menjadi 0; kapasitas internal tidak dijamin berubah.

Catatan Konkurensi #

MutableList dari mutableListOf tidak aman untuk akses paralel. Jika dua thread memanggil add secara bersamaan tanpa sinkronisasi, kita berisiko mendapat data terkorup atau ConcurrentModificationException.

Untuk skenario multi-thread, pilih alternatif berikut:

  • Collections.synchronizedList(mutableListOf(...)) — membungkus list dengan sinkronisasi method-level.
  • CopyOnWriteArrayList — aman untuk iterasi paralel dengan biaya salinan pada setiap mutasi.
  • Channel atau actor — membatasi mutasi ke satu thread dan mengirim permintaan perubahan melalui pesan.

Referensi Lanjutan #

Dengan kemampuan mutasi MutableList di tangan, kita beranjak ke collection yang menolak duplikat: Set.