Pada chapter data class kita melihat bahwa kompilator Kotlin otomatis menghasilkan lima method utama: equals, hashCode, toString, copy, dan componentN. Dua method terakhir sering luput diperhatikan namun sangat berguna — copy memungkinkan pembuatan instance baru yang hampir sama dengan instance lama, dan componentN mendukung destrukturisasi yang membuat kode pengaksesan property lebih ringkas. Pada chapter ini kita akan memakai method copy dengan modifikasi sebagian property, melakukan destrukturisasi pada instance dan dalam loop, serta melihat pola umum seperti swap dua value memanfaatkan destrukturisasi.

Method copy #

Method copy dihasilkan otomatis untuk setiap property di primary constructor data class. Signature method ini menerima parameter opsional untuk setiap property — bila parameter tidak diberikan, nilai dari instance asli dipakai. Bila parameter diberikan, nilai baru menggantikan nilai asli pada instance hasil.

kotlin Contoh: copy dengan modifikasi sebagian
val u1 = Pengguna("Budi", 25, "Jakarta")
val u3 = u1.copy(umur = 26)
// u3 = Pengguna(nama=Budi, umur=26, kota=Jakarta)

Method copy tidak mengubah instance asli — instance u1 tetap memiliki umur=25. Pola ini sesuai dengan konsep immutable data — instance yang sudah dibuat tidak diubah; perubahan dibuat dengan membuat instance baru. Karena copy mengembalikan instance baru, dua instance yang berbeda tetap ada di heap — operator === mengembalikan false untuk instance asli dan hasil copy.

Method componentN dan Destrukturisasi #

Kompilator Kotlin menghasilkan method component1(), component2(), dan seterusnya untuk setiap property di primary constructor data class. Method ini mengembalikan nilai property ke-N. Method componentN mendukung sintaks destrukturisasi — val (a, b, c) = instance — yang memanggil component1(), component2(), component3() untuk mengisi masing-masing variabel.

kotlin Contoh: destrukturisasi sederhana
val u1 = Pengguna("Budi", 25, "Jakarta")
val (nama, umur, kota) = u1
// nama = "Budi", umur = 25, kota = "Jakarta"

Destrukturisasi tidak terbatas pada data class. Pair, Triple, Map.Entry, dan class lain yang menyediakan method componentN juga dapat didestrukturisasi. Pola ini sering muncul di loop for ((key, value) in map) untuk iterasi entry map.

Contoh Lengkap #

Contoh berikut mendemonstrasikan method copy dengan modifikasi sebagian, destrukturisasi pada instance, destrukturisasi di loop list dan map, destrukturisasi parsial dengan _, dan pola swap dua poin memanfaatkan destrukturisasi.

kotlin
// copy_dan_component_functions.kt
// Mendemonstrasikan `copy` dan `componentN` (destructuring) untuk data class.

data class Pengguna(val nama: String, val umur: Int, val kota: String)

data class Buku(
    val judul: String,
    val penulis: String,
    val tahun: Int,
    val harga: Int = 0,
)

data class Item(val id: Long, val nama: String, val qty: Int) {
    fun total(harga: Int): Int = qty * harga
}

data class Poin(val x: Int, val y: Int)

fun main() {
    val u1 = Pengguna("Budi", 25, "Jakarta")
    println("Asli: $u1")

    // copy tanpa argumen: salinan identik.
    val u2 = u1.copy()
    println("Copy tanpa modifikasi: $u2")
    println("u1 === u2 ? ${u1 === u2}")
    println("u1 == u2 ? ${u1 == u2}")
    println()

    // copy dengan modifikasi sebagian field.
    val u3 = u1.copy(umur = 26)
    println("Copy umur=26: $u3")
    val u4 = u1.copy(nama = "Andi", kota = "Bandung")
    println("Copy nama=Andi, kota=Bandung: $u4")
    println("Asli tetap: $u1")
    println()

    // Destructuring via componentN.
    val (nama, umur, kota) = u1
    println("Destructure u1 -> nama=$nama, umur=$umur, kota=$kota")
    println("Komponen 1 (component1): ${u1.component1()}")
    println("Komponen 2 (component2): ${u1.component2()}")
    println("Komponen 3 (component3): ${u1.component3()}")
    println()

    // Destructuring di loop.
    val bukuList = listOf(
        Buku("Belajar Kotlin", "Budi", 2026, 75_000),
        Buku("Belajar Python", "Siti", 2024, 80_000),
        Buku("Belajar Go", "Dewi", 2025, 90_000),
    )
    println("Daftar buku (destructuring di loop):")
    for ((judul, penulis, tahun, harga) in bukuList) {
        println("  - $judul oleh $penulis ($tahun) — Rp$harga")
    }
    println()

    // Destructuring parsial dengan _.
    val (judul, _, _, harga) = bukuList[0]
    println("Destructure parsial -> judul=$judul, harga=$harga")
    println()

    // Destructuring untuk pemetaan key-value map.
    val kodePos = mapOf(
        "Jakarta" to 10110,
        "Bandung" to 40111,
        "Surabaya" to 60111,
    )
    println("Daftar kode pos (destructuring map entry):")
    for ((kota, kode) in kodePos) {
        println("  $kota: $kode")
    }
    println()

    // Pemakaian componentN dengan function yang mengembalikan data class.
    val item = Item(1L, "Buku Tulis", 3)
    val (idItem, namaItem, qtyItem) = item
    println("Item -> id=$idItem, nama=$namaItem, qty=$qtyItem")
    println("Total harga (5000 x ${item.qty}): ${item.total(5000)}")
    println()

    // Swap dua poin via destructuring.
    var p1 = Poin(1, 2)
    var p2 = Poin(3, 4)
    println("Sebelum swap: p1=$p1, p2=$p2")
    p1 = p2.also { p2 = p1 }
    println("Setelah swap: p1=$p1, p2=$p2")
}

Output yang Dihasilkan:

text
Asli: Pengguna(nama=Budi, umur=25, kota=Jakarta)
Copy tanpa modifikasi: Pengguna(nama=Budi, umur=25, kota=Jakarta)
u1 === u2 ? false
u1 == u2 ? true

Copy umur=26: Pengguna(nama=Budi, umur=26, kota=Jakarta)
Copy nama=Andi, kota=Bandung: Pengguna(nama=Andi, umur=25, kota=Bandung)
Asli tetap: Pengguna(nama=Budi, umur=25, kota=Jakarta)

Destructure u1 -> nama=Budi, umur=25, kota=Jakarta
Komponen 1 (component1): Budi
Komponen 2 (component2): 25
Komponen 3 (component3): Jakarta

Daftar buku (destructuring di loop):
  - Belajar Kotlin oleh Budi (2026) — Rp75000
  - Belajar Python oleh Siti (2024) — Rp80000
  - Belajar Go oleh Dewi (2025) — Rp90000

Destructure parsial -> judul=Belajar Kotlin, harga=75000

Daftar kode pos (destructuring map entry):
  Jakarta: 10110
  Bandung: 40111
  Surabaya: 60111

Item -> id=1, nama=Buku Tulis, qty=3
Total harga (5000 x 3): 15000

Sebelum swap: p1=Poin(x=1, y=2), p2=Poin(x=3, y=4)
Setelah swap: p1=Poin(x=3, y=4), p2=Poin(x=1, y=2)

Mari kita bedah kode di atas:

  1. val u2 = u1.copy(): copy tanpa argumen membuat salinan identik. u1 === u2 mengembalikan false — instance berbeda di heap, namun u1 == u2 mengembalikan true karena equals membandingkan struktur property.
  2. val u3 = u1.copy(umur = 26): copy dengan satu argumen — umur diganti 26, dua property lain (nama, kota) tetap membawa nilai dari u1. Hasilnya Pengguna(nama=Budi, umur=26, kota=Jakarta). Property yang tidak disebut tetap membawa nilai asli.
  3. val u4 = u1.copy(nama = "Andi", kota = "Bandung"): copy dengan dua argumen. Hanya umur yang dipertahankan dari instance asli.
  4. val (nama, umur, kota) = u1: destrukturisasi. Sintaks ini memanggil u1.component1() untuk mengisi nama, u1.component2() untuk mengisi umur, dan u1.component3() untuk mengisi kota. Tanpa destrukturisasi, kita harus menulis tiga baris val nama = u1.nama; val umur = u1.umur; val kota = u1.kota.
  5. u1.component1(), u1.component2(), u1.component3(): memanggil method componentN secara eksplisit. Output membuktikan component1() mengembalikan "Budi", component2() mengembalikan 25, dan component3() mengembalikan "Jakarta".
  6. for ((judul, penulis, tahun, harga) in bukuList): destrukturisasi di loop. Setiap iterasi mengambil satu instance Buku dari list dan mendestrukturisasi ke empat variabel. Sintaks ini membuat body loop lebih ringkas — tidak perlu menulis buku.judul, buku.penulis, dst.
  7. val (judul, _, _, harga) = bukuList[0]: destrukturisasi parsial memakai _ untuk property yang tidak diinginkan. Kita hanya peduli judul dan harga, jadi penulis dan tahun diabaikan dengan _. Tanpa _, kompilator akan mengeluh karena jumlah variabel tidak sesuai jumlah property.
  8. for ((kota, kode) in kodePos): destrukturisasi entry map. Setiap iterasi mengambil satu Map.Entry<String, Int>. Entry menyediakan component1() (key) dan component2() (value), sehingga dapat didestrukturisasi menjadi (kota, kode). Pola ini menggantikan pemanggilan verbose entry.key dan entry.value.
  9. val (idItem, namaItem, qtyItem) = item: destrukturisasi instance data class Item. Variabel dapat diberi nama bebas — tidak harus sama dengan nama property asli.
  10. p1 = p2.also { p2 = p1 }: pola swap memanfaatkan also. Fungsi also menerima lambda yang menerima receiver (p2) sebagai parameter it. Lambda dieksekusi dulu (mengisi p2 = p1), kemudian nilai receiver (p2 asli) dikembalikan dan ditugaskan ke p1. Hasil akhir: nilai p1 dan p2 bertukar.

Pola Umum Destrukturisasi #

Destrukturisasi muncul di banyak pola umum Kotlin:

  • Loop list: for ((nama, umur) in daftarPengguna) lebih ringkas dari for (p in daftarPengguna) { val nama = p.nama; val umur = p.umur }.
  • Loop map: for ((k, v) in map) menggantikan for (entry in map.entries) { entry.key; entry.value }.
  • Return function: function yang mengembalikan Pair atau Triple dapat didestrukturisasi di pemanggil — val (status, pesan) = validasi(input).
  • Property ignore: gunakan _ untuk property yang tidak diperlukan — menjaga kode tetap jelas tentang variabel yang dipakai.

Referensi Lanjutan #

  • Data classes — bagian copy() dan componentN() yang dihasilkan data class

Ringkasan #

Method copy menghasilkan instance baru dengan opsi mengganti sebagian property — instance asli tidak berubah. Method componentN mendukung destrukturisasi yang membuat pengaksesan property lebih ringkas, baik dalam deklarasi variabel, loop list, maupun loop map. Kedua method dihasilkan otomatis oleh data class dan merupakan alat utama untuk bekerja dengan data secara idiomatik di Kotlin. Pada chapter OOP Design Principles berikutnya, kita akan merangkum prinsip desain berorientasi object yang membimbing penggunaan fitur-fitur Kotlin secara sehat.