M
Mr Sugiarto
Developer
08 Sep 2026 5 min read

Di bab sebelumnya kita sudah lihat dua bahan baku functional programming di Kotlin: extension function dan higher-order function. Sekarang saatnya lihat gabungan keduanya dalam bentuk yang paling sering kamu pakai sehari-hari di kode backend: operasi pada List seperti filter, map, reduce, dan groupBy. Ini semua sebenarnya extension function higher-order pada List — tepat seperti yang kita bahas di bab lalu, cuma sudah disediakan oleh standard library Kotlin.

Studi Kasus: Keranjang Belanja

CollectionsBasicsTest.kt memakai studi kasus yang relevan langsung dengan domain yang akan kita bangun mulai Fase 1: keranjang belanja untuk Order Service. Setiap item direpresentasikan sebagai data class — persis konsep yang kita pelajari di bab 2:

data class LineItem(val itemName: String, val category: String, val price: BigDecimal, val quantity: Int)

private val cart = listOf(
    LineItem("Keyboard Mechanical", "Elektronik", BigDecimal("750000"), 1),
    LineItem("Mouse Wireless", "Elektronik", BigDecimal("250000"), 2),
    LineItem("Buku Kotlin", "Buku", BigDecimal("120000"), 1),
    LineItem("Notebook", "Alat Tulis", BigDecimal("15000"), 5)
)

cart adalah List<LineItem> immutable berisi empat item lintas kategori. Semua operasi berikut kita jalankan di atas cart yang sama ini.

filter: Menyaring Berdasarkan Kondisi

filter mengembalikan list baru yang isinya cuma elemen-elemen yang lolos kondisi (lambda yang mengembalikan Boolean):

@Test
fun `filter menyaring item berdasarkan kondisi`() {
    val elektronik = cart.filter { it.category == "Elektronik" }
    assertEquals(2, elektronik.size)
}

Bandingkan dengan cara imperatif — bikin list kosong, loop tiap item, cek kondisi, add() kalau lolos:

val elektronik = mutableListOf<LineItem>()
for (item in cart) {
    if (item.category == "Elektronik") {
        elektronik.add(item)
    }
}

Enam baris kode imperatif itu setara dengan cart.filter { it.category == "Elektronik" } — satu baris, dan yang lebih penting: nama fungsinya (filter) langsung bilang apa yang terjadi, sementara versi loop memaksa pembaca menelusuri bagaimana langkah demi langkah untuk menyimpulkan bahwa ini sebuah filter.

map: Mengubah Tiap Item ke Bentuk Lain

map mentransformasi tiap elemen jadi bentuk baru, hasilnya list baru dengan jumlah elemen yang sama:

@Test
fun `map mengubah tiap item jadi bentuk lain`() {
    val itemNames = cart.map { it.itemName }
    assertEquals(listOf("Keyboard Mechanical", "Mouse Wireless", "Buku Kotlin", "Notebook"), itemNames)
}

Di sini kita mengubah List<LineItem> jadi List<String> — cuma mengambil nama item-nya. Pola ini nanti sangat umum di layer Spring Boot: mengubah List<Order> (entity database) jadi List<OrderResponseDto> (bentuk yang dikirim sebagai JSON response) biasanya persis satu baris .map { it.toDto() }.

reduce: Mengakumulasi Jadi Satu Nilai

reduce mengakumulasi seluruh elemen jadi satu nilai tunggal, dengan lambda yang menerima akumulator (acc) dan elemen saat ini, mengembalikan akumulator baru. Test ini menghitung total belanja: dikalikan dulu tiap item (price * quantity) lewat map, baru dijumlahkan semuanya lewat reduce:

@Test
fun `reduce mengakumulasi seluruh item jadi satu nilai`() {
    val totalBelanja = cart
        .map { it.price.multiply(BigDecimal(it.quantity)) }
        .reduce { acc, subtotal -> acc.add(subtotal) }

    assertEquals(BigDecimal("1445000"), totalBelanja)
}

Perhatikan bagaimana map dan reduce dirangkai (chained) langsung — hasil map (list subtotal per item) langsung jadi input reduce. Ini gaya yang sangat umum di Kotlin: memecah transformasi jadi beberapa langkah kecil yang masing-masing jelas namanya, dirangkai jadi satu alur baca dari atas ke bawah. Versi imperatifnya butuh variable akumulator yang dideklarasikan di luar loop lalu dimutasi di dalamnya — lebih rawan salah, misalnya lupa inisialisasi BigDecimal.ZERO atau salah taruh urutan kalikan/jumlahkan di tengah loop panjang.

groupBy: Mengelompokkan Berdasarkan Key

groupBy mengelompokkan elemen jadi Map<K, List<T>>, di mana K adalah hasil lambda key-nya:

@Test
fun `groupBy mengelompokkan item berdasarkan key tertentu`() {
    val byCategory = cart.groupBy { it.category }

    assertEquals(setOf("Elektronik", "Buku", "Alat Tulis"), byCategory.keys)
    assertEquals(2, byCategory["Elektronik"]?.size)
}

Satu baris cart.groupBy { it.category } menghasilkan Map<String, List<LineItem>> — tiga kategori sebagai key, masing-masing berisi list item yang termasuk kategori itu. Perhatikan juga byCategory["Elektronik"]?.size — akses Map dengan key di Kotlin mengembalikan tipe nullable (List<LineItem>?), karena key yang diminta bisa saja tidak ada; ini konsisten dengan null safety yang kita bahas di bab pertama, jadi kita perlu ?. lagi di sini.

Kenapa Gaya Functional Lebih Terbaca

Empat operasi ini — filter, map, reduce, groupBy — punya kesamaan pola: masing-masing mengubah satu koleksi jadi koleksi (atau nilai) lain, lewat satu aturan yang dideklarasikan sebagai lambda, tanpa variable perantara yang harus di-manage manual (index loop, flag, accumulator yang diinisialisasi di luar). Efeknya nyata saat beberapa operasi dirangkai — seperti gabungan map lalu reduce di atas — karena setiap tahap punya nama yang menjelaskan maksudnya sendiri, alih-alih satu blok loop panjang yang mencampur semua logika sekaligus dan memaksa pembaca menyimulasikan eksekusinya baris demi baris untuk mengerti apa yang sebenarnya dihitung.

Sejauh ini semua kode yang kita bahas berjalan sinkron dan sekejap. Di dunia backend nyata, kita sering perlu menunggu I/O — memanggil service lain, query database — tanpa memblokir thread yang berharga. Itu topik bab berikutnya: coroutines dasar di Kotlin.

M
Mr Sugiarto

Developer

Bagian dari Series: Kotlin Backend Microservice - Belajar Fundamental sampai Microservice Nyata

Belajar Kotlin + Spring Boot dari fundamental bahasa (null safety, OOP, coroutines) sampai membangun sistem microservice sungguhan - REST API, Postgre...

Lihat Series Lengkap