Map

Map #

Map adalah koleksi pasangan kunci-nilai (key-value pairs) di mana setiap kunci bersifat unik. Ia menjawab pertanyaan “apa nilai yang terhubung dengan kunci ini?” dalam waktu konstan O(1) — jauh lebih efisien dari mencari di list. Seperti List, Kotlin membedakan Map (read-only) dari MutableMap (bisa dimodifikasi) di level tipe. Di balik layar, Kotlin menggunakan implementasi JVM seperti HashMap, LinkedHashMap, atau TreeMap, masing-masing dengan karakteristik yang berbeda. Artikel ini membahas Map secara menyeluruh: dari pembuatan dan akses dasar, berbagai implementasi yang tersedia, operasi fungsional, hingga pola-pola penggunaan yang idiomatis.

Membuat Map #

// mapOf() — immutable, pasangan ditulis dengan infix 'to'
val ibuKota = mapOf(
    "Indonesia" to "Jakarta",
    "Jepang"    to "Tokyo",
    "Prancis"   to "Paris",
    "Brazil"    to "Brasilia"
)

// emptyMap() — map kosong
val kosong = emptyMap<String, Int>()

// mutableMapOf() — bisa dimodifikasi
val skor = mutableMapOf(
    "Budi"  to 85,
    "Sari"  to 92,
    "Ahmad" to 78
)

// buildMap — DSL builder untuk konstruksi yang lebih fleksibel
val konfigurasi = buildMap {
    put("host", "localhost")
    put("port", "5432")
    put("database", "myapp")
    if (System.getenv("DEBUG") == "true") {
        put("logLevel", "DEBUG")
    }
}

Tipe Pasangan Kunci-Nilai #

to adalah fungsi infix yang membuat Pair<K, V>. Kamu bisa juga membuat map dari list of pair:

val pasangan = listOf(
    "satu" to 1,
    "dua"  to 2,
    "tiga" to 3
)
val dariPasangan = pasangan.toMap()

// Atau dari dua list terpisah
val kunci = listOf("a", "b", "c")
val nilai = listOf(1, 2, 3)
val dariZip = kunci.zip(nilai).toMap()
println(dariZip)  // {a=1, b=2, c=3}

Implementasi Map: Memilih yang Tepat #

Di JVM, ada tiga implementasi utama Map. Kotlin secara default menggunakan LinkedHashMap (lewat mapOf dan mutableMapOf) yang mempertahankan urutan insert.

ImplementasiUrutanPerformaKapan Digunakan
HashMapTidak dijaminO(1) rata-rataPerforma maksimal, urutan tidak penting
LinkedHashMapUrutan insert (default)O(1) rata-rataKetika urutan insert perlu dipertahankan
TreeMapUrutan natural keyO(log n)Ketika perlu iterasi dalam urutan terurut
// HashMap — performa, urutan tidak dijamin
val hashMap = HashMap<String, Int>()
hashMap["b"] = 2
hashMap["a"] = 1
hashMap["c"] = 3
println(hashMap)  // urutan tidak terduga

// LinkedHashMap — pertahankan urutan insert (default mapOf)
val linkedMap = LinkedHashMap<String, Int>()
linkedMap["b"] = 2
linkedMap["a"] = 1
linkedMap["c"] = 3
println(linkedMap)  // {b=2, a=1, c=3} — urutan insert

// TreeMap — urutan natural key (alphabetical untuk String)
val treeMap = java.util.TreeMap<String, Int>()
treeMap["b"] = 2
treeMap["a"] = 1
treeMap["c"] = 3
println(treeMap)  // {a=1, b=2, c=3} — urutan alphabet

// sortedMapOf — shortcut untuk TreeMap
val terurut = sortedMapOf("banana" to 2, "apple" to 1, "cherry" to 3)
println(terurut)  // {apple=1, banana=2, cherry=3}

Mengakses Elemen #

val populasi = mapOf(
    "Jakarta"   to 10_500_000,
    "Surabaya"  to 2_800_000,
    "Bandung"   to 2_400_000,
    "Medan"     to 2_200_000
)

// Akses dengan operator [] — kembalikan null jika key tidak ada
println(populasi["Jakarta"])    // 10500000
println(populasi["Bali"])       // null

// get() — identik dengan []
println(populasi.get("Bandung")) // 2400000

// getOrDefault — kembalikan nilai default jika key tidak ada
println(populasi.getOrDefault("Bali", 0))    // 0

// getOrElse — hitung nilai default secara lazy
println(populasi.getOrElse("Bali") {
    println("Menghitung populasi default...")
    500_000
})

// getValue — lempar NoSuchElementException jika key tidak ada
println(populasi.getValue("Medan"))   // 2200000
// populasi.getValue("Bali")          // ✗ NoSuchElementException

// getOrPut (hanya MutableMap) — ambil nilai atau sisipkan default
val cache = mutableMapOf<String, Int>()
val hasilHitung = cache.getOrPut("fibonacci-10") {
    println("Menghitung fibonacci...")
    55  // dihitung hanya sekali, disimpan ke cache
}
println(cache.getOrPut("fibonacci-10") { 99 })  // 55 — dari cache, 99 tidak dihitung

Informasi Struktur Map #

val data = mapOf("a" to 1, "b" to 2, "c" to 3)

println(data.size)          // 3
println(data.isEmpty())     // false
println(data.isNotEmpty())  // true

// Akses keys, values, entries
println(data.keys)    // [a, b, c]
println(data.values)  // [1, 2, 3]
println(data.entries) // [a=1, b=2, c=3]

// Cek keberadaan
println("a" in data)               // true (cek key)
println(data.containsKey("z"))     // false
println(data.containsValue(2))     // true

Modifikasi MutableMap #

val inventaris = mutableMapOf(
    "Laptop"   to 10,
    "Mouse"    to 50,
    "Keyboard" to 25
)

// Tambah atau ganti
inventaris["Monitor"] = 8        // tambah key baru
inventaris["Laptop"] = 12        // ganti nilai yang ada
inventaris.put("Headset", 15)    // identik dengan []

// Tambah banyak sekaligus
inventaris.putAll(mapOf("Webcam" to 20, "Speaker" to 7))

// Hapus
inventaris.remove("Mouse")                      // hapus berdasarkan key
inventaris.remove("Monitor", 8)                 // hapus hanya jika key-value cocok
inventaris.entries.removeIf { it.value < 10 }   // hapus berdasarkan kondisi

// Update nilai yang ada
inventaris["Laptop"] = (inventaris["Laptop"] ?: 0) + 5  // tambah 5 dari nilai lama

// merge — gabung dengan logika kustom
inventaris.merge("Keyboard", 10) { lama, tambahan -> lama + tambahan }
// jika "Keyboard" ada: nilai baru = lama + tambahan
// jika tidak ada: tambahkan dengan nilai 10

// compute — hitung ulang nilai untuk key
inventaris.compute("Laptop") { _, stokLama ->
    if (stokLama == null) 1 else stokLama + 1
}

println(inventaris)

Iterasi #

val harga = mapOf("Kopi" to 15_000, "Teh" to 10_000, "Jus" to 20_000)

// Destructuring di for loop — paling umum
for ((menu, hargaItem) in harga) {
    println("$menu: Rp${"%,d".format(hargaItem)}")
}

// forEach dengan lambda
harga.forEach { (menu, hargaItem) ->
    println("$menu → Rp${"%,d".format(hargaItem)}")
}

// Iterasi hanya key atau value
harga.keys.forEach { println(it) }
harga.values.forEach { println(it) }

// Iterasi entries (akses penuh ke Map.Entry)
harga.entries.forEach { entry ->
    println("${entry.key}: ${entry.value}")
}

// forEachIndexed tidak ada langsung di Map,
// tapi bisa via entries.forEachIndexed
harga.entries.forEachIndexed { i, (menu, hargaItem) ->
    println("$i. $menu: Rp${"%,d".format(hargaItem)}")
}

Transformasi Fungsional #

Semua operasi di bawah menghasilkan map atau koleksi baru — map asli tidak dimodifikasi.

val nilai = mapOf("Matematika" to 85, "Fisika" to 72, "Kimia" to 90, "Biologi" to 68)

// filter — saring berdasarkan entry (key dan value keduanya tersedia)
val lulus = nilai.filter { (_, v) -> v >= 75 }
println(lulus)  // {Matematika=85, Kimia=90}

// filterKeys — saring berdasarkan key saja
val sainsKeras = nilai.filterKeys { it in listOf("Fisika", "Kimia") }
println(sainsKeras)  // {Fisika=72, Kimia=90}

// filterValues — saring berdasarkan value saja
val nilaiTinggi = nilai.filterValues { it >= 80 }
println(nilaiTinggi)  // {Matematika=85, Kimia=90}

// mapValues — transformasi semua value
val nilaiNormalisasi = nilai.mapValues { (_, v) -> v / 100.0 }
println(nilaiNormalisasi)  // {Matematika=0.85, Fisika=0.72, ...}

// mapKeys — transformasi semua key
val singkatan = nilai.mapKeys { (k, _) -> k.take(3).uppercase() }
println(singkatan)  // {MAT=85, FIS=72, KIM=90, BIO=68}

// map — transformasi entry menjadi list (menghasilkan List, bukan Map)
val deskripsi = nilai.map { (mapel, n) ->
    "$mapel: $n (${if (n >= 75) "Lulus" else "Remedial"})"
}
deskripsi.forEach { println(it) }

// any, all, none, count pada Map
println(nilai.any { (_, v) -> v >= 90 })          // true
println(nilai.all { (_, v) -> v >= 60 })           // true
println(nilai.count { (_, v) -> v >= 75 })         // 2
println(nilai.none { (_, v) -> v > 100 })          // true

// Agregasi nilai
println(nilai.values.average())  // 78.75
println(nilai.values.sum())      // 315
println(nilai.maxByOrNull { it.value })   // Kimia=90
println(nilai.minByOrNull { it.value })   // Biologi=68

Menggabungkan Map #

val mapA = mapOf("a" to 1, "b" to 2, "c" to 3)
val mapB = mapOf("b" to 20, "c" to 30, "d" to 40)

// Operator + — gabung, key dari mapB menimpa mapA jika konflik
val gabungan = mapA + mapB
println(gabungan)  // {a=1, b=20, c=30, d=40}

// Operator - — hapus key
val dikurangi = mapA - "b"
println(dikurangi)  // {a=1, c=3}

val dikurangiBeberapa = mapA - setOf("a", "c")
println(dikurangiBeberapa)  // {b=2}

// Gabung dengan logika kustom (resolve konflik secara manual)
fun <K, V> gabungDenganLogika(
    mapA: Map<K, V>,
    mapB: Map<K, V>,
    resolve: (K, V, V) -> V
): Map<K, V> {
    val hasil = mapA.toMutableMap()
    mapB.forEach { (key, nilaiB) ->
        hasil.merge(key, nilaiB) { nilaiA, b -> resolve(key, nilaiA, b) }
    }
    return hasil
}

// Contoh: gabung stok, ambil nilai terbesar saat konflik
val stokGudangA = mapOf("Laptop" to 5, "Mouse" to 30, "Keyboard" to 10)
val stokGudangB = mapOf("Laptop" to 8, "Monitor" to 15, "Keyboard" to 7)

val totalStok = gabungDenganLogika(stokGudangA, stokGudangB) { _, a, b -> a + b }
println(totalStok)  // {Laptop=13, Mouse=30, Keyboard=17, Monitor=15}

Konversi dari Koleksi Lain #

data class Mahasiswa(val nim: String, val nama: String, val ipk: Double)

val daftarMahasiswa = listOf(
    Mahasiswa("2021001", "Budi Santoso", 3.85),
    Mahasiswa("2021002", "Sari Dewi", 3.92),
    Mahasiswa("2021003", "Ahmad Fauzi", 3.71)
)

// associateBy — buat Map dengan key dari transformasi elemen
val mapByNim = daftarMahasiswa.associateBy { it.nim }
println(mapByNim["2021001"]?.nama)  // Budi Santoso

// associateWith — buat Map dengan value dari transformasi elemen
val mapNamaKeIpk = daftarMahasiswa.associateWith { it.ipk }
// Map<Mahasiswa, Double> — objek Mahasiswa sebagai key

// associate — bebas tentukan key dan value
val nimKeNama = daftarMahasiswa.associate { it.nim to it.nama }
println(nimKeNama)  // {2021001=Budi Santoso, 2021002=Sari Dewi, ...}

// groupBy — kelompokkan jadi Map<K, List<V>>
val mhsPerAngkatan = daftarMahasiswa.groupBy { it.nim.take(4) }
mhsPerAngkatan.forEach { (angkatan, list) ->
    println("Angkatan $angkatan: ${list.map { it.nama }}")
}

// toMap dari list of Pair
val pasangan = listOf("x" to 10, "y" to 20, "z" to 30)
val mapDariPasangan = pasangan.toMap()
println(mapDariPasangan)  // {x=10, y=20, z=30}

Pola Penggunaan Umum #

Cache / Memo Table #

// Memoization — simpan hasil komputasi mahal
val cache = mutableMapOf<Int, Long>()

fun fibonacci(n: Int): Long {
    if (n <= 1) return n.toLong()
    return cache.getOrPut(n) {
        fibonacci(n - 1) + fibonacci(n - 2)
    }
}

println(fibonacci(50))  // 12586269025 — cepat karena di-cache

Frekuensi / Histogram #

val teks = "hello world kotlin is awesome kotlin is great"
val kata = teks.split(" ")

// Hitung frekuensi setiap kata
val frekuensi = kata.groupingBy { it }.eachCount()
println(frekuensi)
// {hello=1, world=1, kotlin=2, is=2, awesome=1, great=1}

// Atau dengan fold
val frekuensiManual = kata.fold(mutableMapOf<String, Int>()) { acc, w ->
    acc.apply { merge(w, 1, Int::plus) }
}

// Urutkan berdasarkan frekuensi tertinggi
val terurut = frekuensi.entries
    .sortedByDescending { it.value }
    .take(3)
    .joinToString { "${it.key}=${it.value}" }
println("Top 3: $terurut")  // Top 3: kotlin=2, is=2, hello=1

Konfigurasi dengan Default #

// Map sebagai konfigurasi yang bisa di-override
val konfigurasiDefault = mapOf(
    "host"     to "localhost",
    "port"     to "5432",
    "poolSize" to "10",
    "timeout"  to "30000"
)

val konfigurasiKustom = mapOf(
    "host"     to "db.production.com",
    "poolSize" to "50"
)

// Gabung: default ditimpa oleh kustom
val konfigurasiAkhir = konfigurasiDefault + konfigurasiKustom
println(konfigurasiAkhir)
// {host=db.production.com, port=5432, poolSize=50, timeout=30000}

Indeks Terbalik #

val nikKeNama = mapOf(
    "3201234567890001" to "Budi Santoso",
    "3201234567890002" to "Sari Dewi",
    "3201234567890003" to "Ahmad Fauzi"
)

// Balik map: nama menjadi key, NIK menjadi value
val namaKeNik = nikKeNama.entries.associate { (nik, nama) -> nama to nik }
println(namaKeNik["Sari Dewi"])  // 3201234567890002

Ringkasan #

  • mapOf sebagai default — selalu mulai dengan map immutable. Ganti ke mutableMapOf hanya jika memang perlu dimodifikasi. Seperti List, ekspos sebagai Map ke luar kelas, simpan sebagai MutableMap secara internal.
  • Akses aman dengan getOrDefault atau getOrElse — hindari getValue kecuali kamu yakin key pasti ada. map[key] mengembalikan null untuk key yang tidak ada — tangani dengan Elvis ?: 0 atau getOrDefault.
  • getOrPut untuk pola cache — ambil nilai yang ada atau sisipkan dan kembalikan nilai baru. Ini adalah pola memoization yang sangat umum dan bersih.
  • LinkedHashMap adalah defaultmapOf dan mutableMapOf menggunakan LinkedHashMap yang mempertahankan urutan insert. Gunakan sortedMapOf untuk urutan natural key, atau HashMap eksplisit jika urutan benar-benar tidak penting.
  • filterKeys, filterValues, mapKeys, mapValues — gunakan versi yang tepat sesuai dimensi yang kamu operasikan. Lebih ekspresif dari filter generik yang harus destructuring setiap kali.
  • Operator + untuk menggabungkanmapA + mapB menghasilkan map baru di mana key yang sama di mapB menimpa mapA. Berguna untuk pola konfigurasi default + override.
  • associateBy untuk indexing — ubah list menjadi map dengan associateBy { it.id } untuk akses O(1) berdasarkan identifier. Jauh lebih efisien dari list.find { it.id == targetId } yang O(n).
  • groupBy untuk agregasi — hasilkan Map<K, List<V>> untuk mengelompokkan data. Kombinasikan dengan mapValues { it.value.sumOf {...} } untuk agregasi per kelompok.

← Sebelumnya: List   Berikutnya: Date & Time →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact