List #
List adalah koleksi berurutan yang paling sering digunakan dalam pemrograman Kotlin sehari-hari. Kotlin membedakan secara tegas antara List (read-only) dan MutableList (bisa dimodifikasi) — perbedaan ini bukan sekadar konvensi, melainkan dikodekan langsung dalam sistem tipe. Ini berarti ketika kamu menerima parameter bertipe List<T>, kamu punya jaminan compiler bahwa kamu tidak bisa mengubah isinya secara tidak sengaja. Di balik layar, keduanya biasanya menggunakan ArrayList dari Java, tapi Kotlin membungkusnya dengan interface yang tepat untuk memastikan safety. Artikel ini membahas List secara menyeluruh: dari pembuatan dan akses dasar hingga seluruh arsenal operasi fungsional yang menjadikan Kotlin begitu ekspresif dalam memproses data koleksi.
Membuat List #
Ada beberapa cara membuat List tergantung kebutuhan:
// listOf() — immutable, tipe diinfer dari elemen
val buah = listOf("Mangga", "Apel", "Jeruk", "Durian")
val angka = listOf(1, 2, 3, 4, 5)
val campuran = listOf(1, "dua", 3.0, true) // List<Any>
// emptyList() — list kosong dengan tipe eksplisit
val kosong = emptyList<String>()
// listOfNotNull() — filter null secara otomatis
val inputDariUser: List<String?> = listOf("Budi", null, "Sari", null, "Ahmad")
val valid = listOfNotNull(*inputDariUser.toTypedArray())
// atau lebih idiomatis:
val valid2 = listOf("Budi", null, "Sari", null, "Ahmad").filterNotNull()
// buildList — builder DSL untuk konstruksi yang fleksibel
val dinamis = buildList {
add("pertama")
addAll(listOf("kedua", "ketiga"))
if (true) add("kondisional")
repeat(3) { add("item-$it") }
}
println(dinamis)
// [pertama, kedua, ketiga, kondisional, item-0, item-1, item-2]
List vs MutableList
#
List adalah interface read-only — hanya bisa dibaca, tidak bisa dimodifikasi. MutableList memperluas List dengan operasi tulis.
// List — hanya bisa dibaca
val daftar: List<String> = listOf("Kotlin", "Java", "Python")
println(daftar[0]) // Kotlin
println(daftar.size) // 3
// daftar.add("Go") // ✗ error — List tidak punya method add()
// daftar[0] = "Swift" // ✗ error — List tidak bisa dimodifikasi
// MutableList — bisa dibaca dan dimodifikasi
val daftarMutable: MutableList<String> = mutableListOf("Kotlin", "Java", "Python")
daftarMutable.add("Go") // tambah di akhir
daftarMutable.add(1, "Swift") // tambah di indeks tertentu
daftarMutable[0] = "Kotlin Terbaru" // ganti elemen
daftarMutable.removeAt(2) // hapus di indeks
daftarMutable.remove("Go") // hapus berdasarkan nilai
println(daftarMutable)
Prinsip: Ekspos Sesempit Mungkin #
// ANTI-PATTERN: ekspos MutableList ke luar kelas
class KeranjangBelanja {
val produk: MutableList<String> = mutableListOf() // kode luar bisa modify bebas!
}
// BENAR: simpan sebagai MutableList secara internal, ekspos sebagai List
class KeranjangBelanja {
private val _produk: MutableList<String> = mutableListOf()
val produk: List<String> get() = _produk // read-only ke luar
fun tambah(produk: String) { _produk.add(produk) }
fun hapus(produk: String) { _produk.remove(produk) }
}
Mengakses Elemen #
val bahasa = listOf("Kotlin", "Java", "Python", "Go", "Rust")
// Akses by indeks — throws IndexOutOfBoundsException jika di luar range
println(bahasa[0]) // Kotlin
println(bahasa[4]) // Rust
// Akses aman — kembalikan null jika indeks di luar range
println(bahasa.getOrNull(10)) // null
println(bahasa.getOrElse(10) { "N/A" }) // N/A
// Elemen pertama dan terakhir
println(bahasa.first()) // Kotlin
println(bahasa.last()) // Rust
println(bahasa.firstOrNull()) // Kotlin (null jika list kosong)
println(bahasa.lastOrNull()) // Rust (null jika list kosong)
// Elemen pertama/terakhir yang memenuhi kondisi
println(bahasa.first { it.length > 4 }) // Kotlin
println(bahasa.last { it.length <= 3 }) // Go
println(bahasa.firstOrNull { it.startsWith("Z") }) // null
// Informasi struktur
println(bahasa.size) // 5
println(bahasa.isEmpty()) // false
println(bahasa.isNotEmpty()) // true
println(bahasa.indices) // 0..4
println(bahasa.lastIndex) // 4
Modifikasi MutableList #
val daftar = mutableListOf("A", "B", "C", "D", "E")
// Menambah elemen
daftar.add("F") // tambah di akhir
daftar.add(2, "X") // sisip di indeks 2
daftar.addAll(listOf("G", "H")) // tambah banyak di akhir
daftar.addAll(0, listOf("Z1", "Z2")) // sisip banyak di indeks 0
// Menghapus elemen
daftar.remove("X") // hapus berdasarkan nilai (yang pertama ditemukan)
daftar.removeAt(0) // hapus di indeks
daftar.removeAll(listOf("G", "H")) // hapus semua yang ada di list argumen
daftar.removeIf { it.startsWith("Z") } // hapus yang memenuhi kondisi
// Mengganti elemen
daftar[0] = "Alpha" // ganti di indeks
daftar.set(1, "Beta") // sama dengan di atas
// Sortir di tempat (in-place)
val angka = mutableListOf(5, 2, 8, 1, 9, 3)
angka.sort() // ascending
println(angka) // [1, 2, 3, 5, 8, 9]
angka.sortDescending() // descending
angka.sortBy { it % 3 } // sort by kriteria
angka.shuffle() // acak urutan
// Kosongkan list
daftar.clear()
Operasi Pencarian dan Pengecekan #
val produk = listOf("Laptop", "Mouse", "Keyboard", "Monitor", "Headset")
// Cek keberadaan
println("Mouse" in produk) // true (operator in)
println(produk.contains("Tablet")) // false
println(produk.containsAll(listOf("Mouse", "Keyboard"))) // true
// Temukan posisi
println(produk.indexOf("Monitor")) // 3
println(produk.lastIndexOf("Mouse")) // 1
println(produk.indexOfFirst { it.length > 6 }) // 0 (Laptop)
println(produk.indexOfLast { it.length <= 5 }) // 1 (Mouse)
// Temukan elemen
println(produk.find { it.startsWith("K") }) // Keyboard
println(produk.findLast { it.contains("o") }) // Monitor
// Cek kondisi pada semua elemen
println(produk.all { it.isNotEmpty() }) // true — semua non-empty
println(produk.any { it.startsWith("Z") }) // false — tidak ada yang mulai "Z"
println(produk.none { it.length > 20 }) // true — tidak ada yang >20 karakter
println(produk.count { it.length > 6 }) // 3 (Keyboard, Monitor, Headset)
Transformasi — Menghasilkan List Baru #
Semua fungsi di bagian ini tidak memodifikasi list asli — mereka menghasilkan list baru.
map dan Variannya
#
val harga = listOf(50_000, 150_000, 75_000, 200_000)
// map — transformasi setiap elemen
val hargaDiskon = harga.map { (it * 0.9).toInt() }
println(hargaDiskon) // [45000, 135000, 67500, 180000]
// mapIndexed — transformasi dengan akses ke indeks
val bernomor = listOf("Apel", "Jeruk", "Mangga")
.mapIndexed { i, buah -> "${i + 1}. $buah" }
println(bernomor) // [1. Apel, 2. Jeruk, 3. Mangga]
// mapNotNull — transformasi + filter null sekaligus
val input = listOf("1", "dua", "3", "empat", "5")
val angkaValid = input.mapNotNull { it.toIntOrNull() }
println(angkaValid) // [1, 3, 5]
// flatMap — transformasi satu-ke-banyak lalu flatten
val kalimat = listOf("halo dunia", "kotlin hebat")
val kata = kalimat.flatMap { it.split(" ") }
println(kata) // [halo, dunia, kotlin, hebat]
// flatten — ratakan list of list menjadi list tunggal
val matriks = listOf(listOf(1, 2, 3), listOf(4, 5), listOf(6, 7, 8, 9))
println(matriks.flatten()) // [1, 2, 3, 4, 5, 6, 7, 8, 9]
filter dan Variannya
#
val nilai = listOf(45, 78, 92, 55, 88, 61, 70, 95, 40)
val lulus = nilai.filter { it >= 70 }
println(lulus) // [78, 92, 88, 70, 95]
val tidakLulus = nilai.filterNot { it >= 70 }
println(tidakLulus) // [45, 55, 61, 40]
// filterIndexed — filter dengan akses ke indeks
val genapIndeks = nilai.filterIndexed { i, _ -> i % 2 == 0 }
println(genapIndeks) // [45, 92, 88, 70, 40] — indeks 0, 2, 4, 6, 8
// partition — split menjadi dua list sekaligus
val (lulus2, gagal) = nilai.partition { it >= 70 }
println("Lulus: $lulus2")
println("Gagal: $gagal")
// filterIsInstance — filter berdasarkan tipe
val campuran: List<Any> = listOf(1, "dua", 3.0, "empat", 5, true)
val stringOnly = campuran.filterIsInstance<String>()
println(stringOnly) // [dua, empat]
Agregasi — Menghitung dari List #
val angka = listOf(3, 1, 4, 1, 5, 9, 2, 6, 5, 3)
// Statistik dasar
println(angka.sum()) // 39
println(angka.count()) // 10
println(angka.min()) // 1
println(angka.max()) // 9
println(angka.average()) // 3.9
// sumOf, minOf, maxOf — untuk tipe yang butuh transformasi
data class Produk(val nama: String, val harga: Int, val stok: Int)
val produk = listOf(
Produk("Laptop", 15_000_000, 5),
Produk("Mouse", 250_000, 50),
Produk("Keyboard", 500_000, 20)
)
println(produk.sumOf { it.harga }) // 15750000
println(produk.minOf { it.harga }) // 250000
println(produk.maxOf { it.harga }) // 15000000
println(produk.maxByOrNull { it.stok }) // Produk(nama=Mouse, ...)
// reduce — akumulasi tanpa nilai awal
val hasil = angka.reduce { akum, nilai -> akum + nilai }
println(hasil) // 39
// fold — akumulasi dengan nilai awal
val jumlahDenganBonus = angka.fold(100) { akum, n -> akum + n }
println(jumlahDenganBonus) // 139
// joinToString — gabungkan jadi String
val bahasa = listOf("Kotlin", "Java", "Python")
println(bahasa.joinToString(", ")) // Kotlin, Java, Python
println(bahasa.joinToString(prefix = "[", postfix = "]", separator = " | "))
// [Kotlin | Java | Python]
println(bahasa.joinToString { it.uppercase() }) // KOTLIN, JAVA, PYTHON
Pengurutan #
val nama = listOf("Charlie", "Alice", "Bob", "Diana", "Eve")
// Urutkan — hasilkan list baru (list asli tidak berubah)
println(nama.sorted()) // [Alice, Bob, Charlie, Diana, Eve]
println(nama.sortedDescending()) // [Eve, Diana, Charlie, Bob, Alice]
println(nama.sortedBy { it.length }) // [Bob, Eve, Alice, Diana, Charlie]
println(nama.sortedByDescending { it.length }) // [Charlie, Diana, Alice, Bob, Eve]
// sortedWith — urutan kustom dengan Comparator
val namaTerurut = nama.sortedWith(compareBy({ it.length }, { it }))
println(namaTerurut) // [Bob, Eve, Alice, Diana, Charlie]
// reversed — balik urutan
println(nama.reversed()) // [Eve, Diana, Bob, Alice, Charlie]
data class Mahasiswa(val nama: String, val ipk: Double, val angkatan: Int)
val mhs = listOf(
Mahasiswa("Budi", 3.85, 2022),
Mahasiswa("Sari", 3.92, 2021),
Mahasiswa("Ahmad", 3.71, 2022),
Mahasiswa("Rina", 3.92, 2020)
)
// Urutkan berdasarkan IPK descending, lalu nama ascending
val terurut = mhs.sortedWith(compareByDescending<Mahasiswa> { it.ipk }.thenBy { it.nama })
terurut.forEach { println("${it.nama}: ${it.ipk} (${it.angkatan})") }
// Rina: 3.92 (2020)
// Sari: 3.92 (2021)
// Budi: 3.85 (2022)
// Ahmad: 3.71 (2022)
Pengelompokan #
data class Transaksi(val kategori: String, val jumlah: Int)
val transaksi = listOf(
Transaksi("Makan", 50_000),
Transaksi("Transport", 30_000),
Transaksi("Makan", 75_000),
Transaksi("Hiburan", 120_000),
Transaksi("Transport", 25_000),
Transaksi("Makan", 45_000)
)
// groupBy — kelompokkan menjadi Map<K, List<V>>
val perKategori = transaksi.groupBy { it.kategori }
perKategori.forEach { (kat, list) ->
println("$kat: ${list.size} transaksi, total Rp${"%,d".format(list.sumOf { it.jumlah })}")
}
// Makan: 3 transaksi, total Rp170,000
// Transport: 2 transaksi, total Rp55,000
// Hiburan: 1 transaksi, total Rp120,000
// groupingBy().eachCount() — hitung jumlah per kelompok
val jumlahPerKategori = transaksi.groupingBy { it.kategori }.eachCount()
println(jumlahPerKategori) // {Makan=3, Transport=2, Hiburan=1}
// chunked — pecah menjadi potongan ukuran tertentu
val halaman = listOf(1..20).flatMap { it.toList() }.chunked(5)
halaman.forEach { println(it) }
// [1, 2, 3, 4, 5]
// [6, 7, 8, 9, 10]
// [11, 12, 13, 14, 15]
// [16, 17, 18, 19, 20]
// windowed — jendela geser
val data = listOf(1.0, 2.0, 3.0, 4.0, 5.0)
val rataRataGerak = data.windowed(3) { it.average() }
println(rataRataGerak) // [2.0, 3.0, 4.0]
Konversi Antar Koleksi #
val daftar = listOf("Budi", "Sari", "Ahmad", "Budi", "Rina", "Sari")
// Ke Set — hapus duplikat
val unik = daftar.toSet()
println(unik) // [Budi, Sari, Ahmad, Rina]
// Ke MutableList — buat salinan yang bisa dimodifikasi
val salinan = daftar.toMutableList()
salinan.add("Tambahan")
// Ke Map — transformasi jadi pasangan key-value
val panjangNama = daftar.distinct().associateWith { it.length }
println(panjangNama) // {Budi=4, Sari=4, Ahmad=5, Rina=4}
// zip — pasangkan dua list
val nama = listOf("Budi", "Sari", "Ahmad")
val nilai = listOf(85, 92, 78)
val pasangan = nama.zip(nilai)
println(pasangan) // [(Budi, 85), (Sari, 92), (Ahmad, 78)]
// unzip — pisahkan list of pair menjadi dua list
val (namaSaja, nilaiSaja) = pasangan.unzip()
println(namaSaja) // [Budi, Sari, Ahmad]
println(nilaiSaja) // [85, 92, 78]
// distinct — hapus duplikat (pertahankan urutan)
println(daftar.distinct()) // [Budi, Sari, Ahmad, Rina]
println(daftar.distinctBy { it.first() }) // [Budi, Sari, Ahmad, Rina]
Sequence — Untuk Performa pada Data Besar #
Secara default, operasi koleksi di Kotlin bersifat eager — setiap operasi langsung dieksekusi dan menghasilkan list intermediate. Untuk pipeline panjang dengan data besar, Sequence memberikan evaluasi lazy yang lebih efisien.
// ANTI-PATTERN: eager — membuat 3 list intermediate untuk 1 juta elemen
val hasilEager = (1..1_000_000)
.toList()
.filter { it % 2 == 0 } // list 1: 500.000 elemen
.map { it * it } // list 2: 500.000 elemen
.take(5) // list 3: 5 elemen
println(hasilEager)
// BENAR: sequence — hanya memproses yang dibutuhkan
val hasilLazy = (1..1_000_000)
.asSequence()
.filter { it % 2 == 0 } // lazy — belum dieksekusi
.map { it * it } // lazy — belum dieksekusi
.take(5) // lazy — belum dieksekusi
.toList() // baru dieksekusi — hanya proses sampai 10 elemen!
println(hasilLazy) // [4, 16, 36, 64, 100]
GUNAKAN List (eager) jika:
✓ Data kecil hingga sedang (ribuan elemen)
✓ Pipeline pendek (1-2 operasi)
✓ Butuh akses random ke elemen intermediate
GUNAKAN Sequence (lazy) jika:
✓ Data sangat besar (ratusan ribu atau lebih)
✓ Pipeline panjang (3+ operasi)
✓ Operasi berpotensi berhenti lebih awal (take, find, first)
✓ Membaca data dari stream/file baris per baris
Ringkasan #
listOfvsmutableListOf— gunakanlistOfsebagai default. Ganti kemutableListOfhanya jika list memang perlu dimodifikasi setelah dibuat. Ekspos ke luar selalu sebagaiList, simpan internal sebagaiMutableList.- Akses aman dengan
getOrNulldangetOrElse— hindariIndexOutOfBoundsExceptiondengan menggunakangetOrNull(i)daripadalist[i]ketika indeks mungkin di luar range.mapNotNulluntuk transformasi + filter sekaligus — lebih efisien darimaplalufilterNotNull. Sangat berguna untuk parsing input yang mungkin tidak valid.partitionuntuk split kondisional —val (lulus, gagal) = nilai.partition { it >= 70 }lebih bersih dari duafilterterpisah.groupByuntuk agregasi per kelompok — menghasilkanMap<K, List<V>>yang bisa langsung diproses. Kombinasikan dengansumOf,count,maxByOrNulluntuk laporan agregat.joinToStringuntuk representasi string — jauh lebih fleksibel dari loop manual. Dukungprefix,postfix,separator, dan transformasi elemen sekaligus.zipdanunzipuntuk list berpasangan — pasangkan dua list denganzipdan pisahkan kembali denganunzip. Berguna untuk memproses data yang berkorelasi.Sequenceuntuk data besar — tambahkan.asSequence()sebelum pipeline panjang di data besar untuk evaluasi lazy yang jauh lebih hemat memori dan waktu.