Map read-only statis setelah dibuat. Begitu kita perlu memperbarui nilai, menambah key baru, atau menghapus pasangan saat program berjalan, MutableMap<K, V> menjadi pilihan. Pada chapter ini kita akan mempraktikkan operasi mutasi utama, pola idiomatik seperti getOrPut dan compute, serta alternatif yang aman untuk akses paralel.

Membuat MutableMap #

MutableMap dibuat dengan mutableMapOf(...). Implementasi default adalah LinkedHashMap, sehingga urutan penyisipan key tetap dipertahankan.

kotlin Contoh: deklarasi MutableMap
val stok: MutableMap<String, Int> = mutableMapOf("apel" to 5, "jeruk" to 3)
val kosong: MutableMap<String, Int> = mutableMapOf()
val hashed: MutableMap<String, Int> = hashMapOf()

Operasi Mutasi Utama #

MetodeefekCatatan
map[k] = v / put(k, v)menambah atau menggantiMengembalikan nilai lama atau null
putIfAbsent(k, v)menambah hanya jika k belum adaMengembalikan nilai yang sudah ada atau null
getOrPut(k) { default }mengambil atau memasukkan defaultIdiom cache ringkas
compute(k) { _, v -> ... }menghitung ulang nilai berdasarkan nilai lamaLambda boleh mengembalikan null untuk menghapus entry
remove(k)menghapus entry dengan key kMengembalikan nilai lama atau null
remove(k, v)menghapus hanya jika nilai cocokMengembalikan Boolean
clear()mengosongkan mapsize menjadi 0

getOrPut: Pola Cache Idiomatik #

Pola umum dalam kode Kotlin adalah mengambil nilai dari map, atau jika belum ada, menghitung lalu menyimpannya. Tanpa getOrPut, kita menulis tiga langkah: cek containsKey, ambil, atau masukkan. Dengan getOrPut, satu baris cukup.

kotlin Contoh: getOrPut
val cache = mutableMapOf<String, Int>()
val panjang = cache.getOrPut("kotlin") { "kotlin".length }

Lambda { "kotlin".length } hanya dievaluasi ketika key "kotlin" tidak ada dalam map.

compute: Mutasi Berdasarkan Nilai Lama #

compute menerima key dan lambda yang menerima key serta nilai lama (bisa null). Lambda mengembalikan nilai baru — jika null, entry dihapus dari map.

kotlin Contoh: compute
stok.compute("apel") { _, v -> v?.let { it * 2 } }   // kalikan dengan 2
stok.compute("apel") { _, _ -> 50 }                  // tuk paksa
stok.compute("jeruk") { _, _ -> null }                // hapus entry

Contoh Lengkap #

kotlin
fun main() {
    val stok: MutableMap<String, Int> = mutableMapOf(
        "apel" to 5,
        "jeruk" to 3,
        "pisang" to 8
    )
    println("Awal: $stok")

    // put: menambah atau mengganti
    stok["mangga"] = 4
    println("Setelah put mangga: $stok")

    stok["apel"] = 10  // mengganti nilai lama
    println("Setelah put apel (ganti): $stok")

    stok.putIfAbsent("apel", 99)  // sudah ada -> tidak diubah
    println("Setelah putIfAbsent apel: $stok")

    stok.putIfAbsent("anggur", 7)
    println("Setelah putIfAbsent anggur: $stok")

    // getOrPut
    val jumlahNanas = stok.getOrPut("nanas") { 0 }
    println("getOrPut nanas: $jumlahNanas, stok=$stok")

    val jumlahApel = stok.getOrPut("apel") { 100 }
    println("getOrPut apel (sudah ada): $jumlahApel")

    // compute: menghitung ulang nilai
    stok.compute("apel") { _, v -> v?.let { it * 2 } }
    println("Setelah compute apel (kali 2): $stok")

    stok.compute("apel") { _, _ -> 50 }
    println("Setelah compute apel (set 50): $stok")

    // remove
    stok.remove("jeruk")
    println("Setelah remove jeruk: $stok")

    stok.remove("pisang", 99)  // nilai tidak cocok -> tidak dihapus
    println("Setelah remove pisang (nilai tidak cocok): $stok")

    // Iterasi mutable + filter
    println("Stok > 5:")
    stok.filter { (_, j) -> j > 5 }.forEach { (k, v) -> println("  $k: $v") }

    // toMutableMap (salinan)
    val salinan = stok.toMutableMap()
    salinan.clear()
    println("Salinan setelah clear: $salinan")
    println("Asli tetap: $stok")

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

Output yang Dihasilkan:

text
Awal: {apel=5, jeruk=3, pisang=8}
Setelah put mangga: {apel=5, jeruk=3, pisang=8, mangga=4}
Setelah put apel (ganti): {apel=10, jeruk=3, pisang=8, mangga=4}
Setelah putIfAbsent apel: {apel=10, jeruk=3, pisang=8, mangga=4}
Setelah putIfAbsent anggur: {apel=10, jeruk=3, pisang=8, mangga=4, anggur=7}
getOrPut nanas: 0, stok={apel=10, jeruk=3, pisang=8, mangga=4, anggur=7, nanas=0}
getOrPut apel (sudah ada): 10
Setelah compute apel (kali 2): {apel=20, jeruk=3, pisang=8, mangga=4, anggur=7, nanas=0}
Setelah compute apel (set 50): {apel=50, jeruk=3, pisang=8, mangga=4, anggur=7, nanas=0}
Setelah remove jeruk: {apel=50, pisang=8, mangga=4, anggur=7, nanas=0}
Setelah remove pisang (nilai tidak cocok): {apel=50, pisang=8, mangga=4, anggur=7, nanas=0}
Stok > 5:
  apel: 50
  pisang: 8
  anggur: 7
Salinan setelah clear: {}
Asli tetap: {apel=50, pisang=8, mangga=4, anggur=7, nanas=0}
MutableMap TIDAK aman untuk akses multi-thread tanpa sinkronisasi

Mari kita bedah baris-baris penting di atas:

  1. stok["mangga"] = 4: subscript di sisi penugasan setara dengan put("mangga", 4). Karena key baru, entry ditambahkan.
  2. stok.putIfAbsent("apel", 99): key "apel" sudah ada (nilai 10), sehingga pemanggilan tidak mengubah apa pun dan mengembalikan nilai yang sudah ada.
  3. stok.getOrPut("nanas") { 0 }: key "nanas" tidak ada, lambda dievaluasi dan hasilnya (0) dimasukkan serta dikembalikan.
  4. stok.compute("apel") { _, v -> v?.let { it * 2 } }: lambda menerima v lama (10) dan mengembalikan 20. Bila v null (key tidak ada), v?.let mengembalikan null dan entry akan dihapus.
  5. stok.remove("pisang", 99): varian dua argumen hanya menghapus jika nilai yang tersimpan sama persis dengan argumen kedua. Karena nilai "pisang" adalah 8 (bukan 99), entry tetap utuh dan metode mengembalikan false.

Alternatif Aman untuk Konkurensi #

MutableMap default tidak aman untuk akses paralel. Untuk skenario multi-thread, pilih alternatif berikut:

ImplementasiSumberKarakteristik
ConcurrentHashMapjava.util.concurrentAman baca-tulis paralel, iterasi weakly-consistent
Collections.synchronizedMapjava.util.CollectionsMembungkus map dengan sinkronisasi method-level
ConcurrentSkipListMapjava.util.concurrentAman paralel + urutan natural / komparator
kotlin Contoh: ConcurrentHashMap
import java.util.concurrent.ConcurrentHashMap

val cacheParalel: MutableMap<String, Int> = ConcurrentHashMap()
cacheParalel["a"] = 1
cacheParalel.compute("a") { _, v -> v?.plus(1) }

Referensi Lanjutan #

Setelah memahami Map dan MutableMap, kita akan beralih ke struktur mirip list tetapi dengan ukuran tetap: Array.