Case class

Data immutable dengan copy, kesetaraan struktural, dan destructuring siap pakai.

Daftar isi

Case class adalah kelas yang dideklarasikan dengan kata kunci case dan dirancang untuk data yang nilainya tetap setelah dibuat. Compiler menghasilkan sejumlah anggota otomatis untuknya, antara lain toString yang menampilkan seluruh field, equals dan hashCode yang membandingkan isi, metode copy untuk membuat salinan dengan sebagian perubahan, serta ekstraktor yang mendukung destructuring. Topik ini membahas kelompok anggota tersebut pada contoh inventaris alat laboratorium, beserta pertimbangan memilih case class dibandingkan dengan kelas biasa.

Anggota yang dihasilkan compiler#

Setiap parameter pada deklarasi case class otomatis menjadi field val yang dapat diakses dari luar dan tidak dapat diubah, sehingga data yang dibawa instance tetap sama sepanjang hidupnya. Objek companion-nya juga mendapat metode apply, sehingga instance dibuat tanpa kata new. Selain itu, compiler menghasilkan toString yang mencetak nama kelas beserta seluruh fieldnya, sehingga isi data dapat diperiksa tanpa menulis kode pemformatan manual.

Program berikut mendeklarasikan case class LabItem untuk catatan alat laboratorium, membuat dua instance dengan isi yang sama, lalu membandingkan dan menyalinnya.

scala lab-item.scala
// case class untuk data alat laboratorium
case class LabItem(name: String, code: String, quantity: Int)

@main def inventory(): Unit =
  val m1 = LabItem("Mikroskop", "MIK-01", 4)
  val m2 = LabItem("Mikroskop", "MIK-01", 4)

  // toString otomatis menampilkan nama kelas beserta seluruh fieldnya
  println(m1)

  // kesetaraan struktural: isi yang dibandingkan, bukan alamat memori
  println(m1 == m2)

  // copy menghasilkan instance baru dengan sebagian field berubah
  val m3 = m1.copy(quantity = 7)
  println(m3)
  println(m1 == m3)
bash
# menjalankan program lab-item.scala
scala-cli run lab-item.scala
text Keluaran
LabItem(Mikroskop,MIK-01,4)
true
LabItem(Mikroskop,MIK-01,7)
false

Baris pertama memperlihatkan keluaran toString yang mencantumkan ketiga field tanpa kode tambahan apa pun. Baris kedua dan keempat adalah hasil perbandingan yang dibahas pada bagian berikutnya, sedangkan baris ketiga adalah salinan yang dibahas setelahnya. Satu deklarasi singkat sudah menyediakan semua anggota tersebut, karena itulah case class menjadi pilihan pertama untuk tipe data pada program Scala.

Kesetaraan struktural antar-instance#

Kesetaraan struktural (structural equality) berarti dua instance dianggap sama apabila seluruh fieldnya bernilai sama, tanpa memandang lokasi keduanya di memori. Operator == pada case class memanggil equals yang dihasilkan compiler dan membandingkan field per field. Kelas biasa yang tidak menimpa equals berperilaku berbeda, yaitu membandingkan identitas referensi, sehingga dua objek dengan isi identik belum tentu dianggap sama.

Pada keluaran program sebelumnya, m1 == m2 bernilai true meskipun keduanya dibuat dari dua ekspresi yang berbeda, karena isi ketiga fieldnya sama. Sebaliknya, m1 == m3 bernilai false karena field quantity-nya berbeda, yaitu 4 dan 7. Perbandingan semacam ini berlaku untuk seluruh field yang dideklarasikan pada parameter utama, sedangkan anggota yang didefinisikan di dalam tubuh kelas tidak ikut dibandingkan.

copy untuk salinan dengan sebagian perubahan#

Metode copy membuat instance baru yang field-fieldnya disalin dari instance sumber, kecuali field yang diganti melalui argumen bernama. Instance sumber tidak tersentuh sama sekali, sehingga variasi data dinyatakan sebagai data baru alih-alih mengubah data yang lama. Cara ini sesuai dengan sifat immutable case class, dan salinannya dapat digunakan bersama kode lain tanpa menimbulkan efek samping.

Pada program sebelumnya, m1.copy(quantity = 7) menghasilkan LabItem(Mikroskop,MIK-01,7). Field name dan code disalin apa adanya dari m1, sedangkan quantity diganti sesuai argumennya. Jumlah argumen pada copy boleh kurang dari jumlah field, sehingga satu field dapat diubah tanpa menulis ulang argumen yang lain.

Destructuring dengan pola val#

Destructuring adalah pembongkaran nilai menjadi beberapa pengikat sekaligus, dan case class mendukungnya karena objek companion-nya mendapat metode unapply. Pola val LabItem(name, code, quantity) memanggil ekstraktor itu lalu mengikat setiap field ke nama lokal yang ditentukan pengembang. Bentuk ini menghilangkan rangkaian akses field berulang, terutama ketika seluruh field memang dibutuhkan.

Program berikut memilih alat dengan stok terkecil dari tiga catatan inventaris menggunakan minBy, kemudian membongkar hasilnya untuk menyusun pesan pemesanan ulang.

scala lab-destructure.scala
// case class alat laboratorium beserta destructuring-nya
case class LabItem(name: String, code: String, quantity: Int)

@main def lowStock(): Unit =
  val items = Vector(
    LabItem("Mikroskop", "MIK-01", 4),
    LabItem("Buret", "BRT-02", 12),
    LabItem("Spektrofotometer", "SPF-03", 2)
  )

  // metode minBy memilih elemen dengan quantity terkecil
  val critical = items.minBy(_.quantity)

  // destructuring: pola val mengikat setiap field ke nama lokal
  val LabItem(name, code, quantity) = critical
  println(s"Pesan ulang: $name ($code), sisa $quantity")
bash
# menjalankan program lab-destructure.scala
scala-cli run lab-destructure.scala
text Keluaran
Pesan ulang: Spektrofotometer (SPF-03), sisa 2

Keluaran memperlihatkan hasil pemilihan beserta isi fieldnya dalam satu kalimat terformat. Nama lokal name, code, dan quantity hanya berlaku pada lingkup setelah pola tersebut, sehingga program tetap ringkas tanpa variabel sementara tambahan. Pola yang sama juga dapat ditulis di dalam for dan pencocokan pola (pattern matching) yang menggunakan unapply yang sama.

Kapan case class dipilih#

Pemilihan antara case class, kelas biasa, dan trait dapat diringkas sebagai berikut.

KebutuhanPilihan yang sesuai
Data dengan kesetaraan menurut isi dan field yang tetapcase class
Objek dengan identitas tersendiri atau state yang berubahkelas biasa
Kontrak perilaku atau implementasi yang dibagikantrait

Case class cocok untuk data domain seperti catatan inventaris, hasil pengukuran, atau baris laporan, karena nilainya dibandingkan dan didistribusikan apa adanya. Kelas biasa lebih tepat ketika identitas objek lebih penting daripada isinya, misalnya penampung atau antrean yang state internalnya berubah sepanjang waktu. Trait dipilih ketika yang dibagikan adalah perilaku, bukan data. Ketiganya saling melengkapi, dan deklarasi case class pun dapat mewarisi trait seperti kelas biasa, sehingga data dan perilaku tetap dapat dipisahkan dengan rapi.

Ringkasan#

  • Parameter case class otomatis menjadi field immutable, sedangkan compiler menghasilkan apply, toString, equals, hashCode, copy, dan unapply pada objek companion-nya.
  • Operator == membandingkan isi field per field, sehingga dua instance dengan data yang sama dianggap sama dan aman digunakan sebagai kunci Map.
  • Metode copy membuat instance baru dengan sebagian field diganti melalui argumen bernama, sementara instance sumber tetap utuh.
  • Pola val pada destructuring menggunakan unapply untuk mengikat setiap field ke nama lokal tanpa variabel sementara.

Referensi#