Data class adalah kelas yang ditandai kata kunci data di depan deklarasinya sehingga compiler menghasilkan serangkaian fungsi otomatis dari properti pada konstruktor utamanya. Fungsi yang dihasilkan meliputi equals, hashCode, toString, copy, dan pasangan componentN untuk destructuring. Topik ini membahas kelompok fungsi tersebut, batasan yang menyertai data class, serta properti yang tidak ikut diperhitungkan. Contoh menggunakan titik koordinat dan catatan gudang, sehingga setiap perilaku dapat diamati langsung dari keluaran program.
Fungsi yang dihasilkan compiler#
Data class tidak menuntut penulisan manual untuk fungsi pembanding dan pencetak. Compiler membaca properti yang dideklarasikan pada konstruktor utama, lalu menghasilkan implementasi equals, hashCode, toString, copy, dan componentN dari properti-properti tersebut. Program berikut memakai dua data class. Titik berperan sebagai pembawa data murni, sedangkan Gudang sengaja memiliki properti tambahan di badan kelas untuk memperlihatkan batasan yang menyertainya.
data class Titik(val x: Int, val y: Int)
data class Gudang(val nama: String) {
var kapasitas: Int = 0
}
fun main() {
val a = Titik(3, 4)
val b = Titik(3, 4)
val c = Titik(5, 1)
println("a: $a")
println("b: $b")
println("a == b: ${a == b}")
println("a == c: ${a == c}")
println("hashCode a == hashCode b: ${a.hashCode() == b.hashCode()}")
val d = a.copy(x = 10)
println("d: $d")
val (x, y) = c
println("komponen c: x=$x, y=$y")
val g1 = Gudang("Utama")
g1.kapasitas = 100
val g2 = Gudang("Utama")
g2.kapasitas = 250
println("g1 == g2: ${g1 == g2}")
println("g1: $g1")
val g3 = g1.copy()
println("kapasitas g3: ${g3.kapasitas}")
}# atur PATH compiler Kotlin dan lokasi kotlin-stdlib
export PATH=/home/kal/my-project/tools/kotlinc/bin:/home/kal/my-project/tools/jdk-merged/bin:$PATH
export STDLIB=/home/kal/my-project/tools/kotlinc/lib/kotlin-stdlib.jar
# kompilasi main.kt menjadi out.jar
kotlinc main.kt -d out.jar
# jalankan program dengan kotlin-stdlib di classpath
java -cp "out.jar:$STDLIB" MainKta: Titik(x=3, y=4)
b: Titik(x=3, y=4)
a == b: true
a == c: false
hashCode a == hashCode b: true
d: Titik(x=10, y=4)
komponen c: x=5, y=1
g1 == g2: true
g1: Gudang(nama=Utama)
kapasitas g3: 0Keluaran di atas memperlihatkan empat hal sekaligus. Dua instans Titik dengan nilai properti yang sama dibandingkan lewat == dan hasilnya true, karena operator itu memanggil equals yang membandingkan x dan y satu per satu. Pencetakan a menghasilkan teks Titik(x=3, y=4) tanpa menulis toString secara manual. Baris hashCode a == hashCode b: true menunjukkan bahwa hash dua instans yang sama juga sama. Tiga baris terakhir menyangkut Gudang, yang perilakunya dijelaskan pada dua bagian selanjutnya.
Perbandingan equals, hashCode, dan toString#
Fungsi equals yang dihasilkan compiler membandingkan tipe objek dan seluruh properti pada konstruktor utama. Dua instans dianggap sama apabila tipenya sama dan setiap propertinya bernilai sama, seperti a dan b pada program di atas. hashCode dihitung dari properti yang sama, sehingga kontrak bahwa dua objek yang sama wajib memiliki hash yang sama terpenuhi tanpa usaha tambahan. toString mencetak nama kelas beserta tiap properti dalam format nama=nilai.
Perilaku Gudang mengonfirmasi cakupan mekanisme ini. Properti kapasitas dideklarasikan di badan kelas, bukan pada konstruktor utama, sehingga g1 dan g2 tetap dianggap sama meskipun kapasitasnya berbeda. Pencetakan g1 pun hanya menampilkan Gudang(nama=Utama) dan melewatkan kapasitas. Compiler memang memperhitungkan konstruktor utama saja, bukan seluruh properti kelas.
Menyalin instans dengan copy#
Fungsi copy membuat instans baru dengan menyalin seluruh nilai properti konstruktor utama. Argumen bernama dapat menyertai pemanggilannya untuk mengubah sebagian nilai saja, seperti a.copy(x = 10) yang menghasilkan titik baru dengan x bernilai 10 dan y tetap 4. Dua properti boleh diubah sekaligus dalam satu pemanggilan, misalnya a.copy(x = 10, y = 20), dan urutan argumannya bebas karena bersifat bernama. Pola ini berguna untuk data class yang propertinya val, karena perubahan dinyatakan sebagai salinan baru alih-alih pengubahan nilai lama.
Satu hal perlu diwaspadai dari keluaran program. Pemanggilan g1.copy() tanpa argumen menghasilkan g3 dengan kapasitas bernilai 0, bukan 100. Fungsi copy hanya menyalin properti konstruktor utama, sedangkan properti di badan kelas diinisialisasi ulang dari nol pada salinannya. Perilaku ini konsisten dengan cara kerjanya, karena copy secara internal memanggil kembali konstruktor utama dengan nilai yang sudah ada, dan kapasitas bukan bagian dari konstruktor tersebut.
Destructuring dengan componentN#
Setiap properti konstruktor utama mendapat fungsi componentN sesuai posisinya, yaitu component1 untuk properti pertama, component2 untuk properti kedua, dan seterusnya. Fungsi inilah yang membuat destructuring bekerja. Pernyataan val (x, y) = c pada program di atas memanggil c.component1() dan c.component2(), lalu menyimpan hasilnya ke dua variabel.
Destructuring juga dapat ditulis langsung pada kepala perulangan for ketika collection berisi data class, sehingga setiap elemen langsung terurai menjadi variabelnya. Pemetaannya berdasarkan posisi, bukan nama. Apabila urutan properti pada deklarasi diubah, hasil penguraian ikut tertukar tanpa pesan error apa pun. Nama yang tidak dibutuhkan boleh diganti garis bawah, misalnya val (_, y) = c, bila hanya sebagian komponen yang diperlukan. Atas alasan itu, destructuring paling aman pada data class dengan jumlah properti yang sedikit dan urutan yang stabil.
Batasan data class#
Data class hanya sah bila konstruktor utamanya memiliki minimal satu parameter, dan setiap parameter harus dinyatakan val atau var. Deklarasi data class Titik tanpa parameter ditolak compiler dengan pesan data class must have at least one primary constructor parameter., sedangkan parameter biasa tanpa val atau var ditolak dengan pesan primary constructor of data class must only have property ('val' / 'var') parameters. Kedua pesan tersebut muncul saat kompilasi, sebelum program sempat dijalankan.
// tidak-dijalankan
data class Titik(x: Int, y: Int)Modifier pewarisan juga terbatas. Data class tidak dapat ditandai abstract, open, sealed, atau inner, karena pembangkitan fungsi otomatis mengasumsikan perbandingan dilakukan pada kelas yang berdiri sendiri. Batasan lain menyangkut cakupan data yang layak. Data class paling tepat untuk kelompok nilai kecil yang mewakili satu gagasan, misalnya koordinat, hasil pengukuran, atau status sebuah tugas. Kelas yang menyimpan perilaku kompleks dan banyak dependensi lebih baik dinyatakan sebagai kelas biasa agar equals dan hashCode-nya ditentukan secara sadar.
Ringkasan#
- Compiler data class menghasilkan
equals,hashCode,toString,copy, dancomponentNdari properti konstruktor utama. equalsmembandingkan seluruh properti konstruktor utama, danhashCodedihitung dari properti yang sama sehingga kontrak keduanya terpenuhi.copymenghasilkan salinan baru yang dapat diubah sebagian lewat argumen bernama, sedangkan properti di badan kelas tidak ikut tersalin.- Konstruktor utama data class wajib memiliki minimal satu parameter
valatauvar, dan kelas tersebut tidak dapat ditandaiabstract,open,sealed, atauinner.
Referensi#
- Data classes — Kotlin Documentation — Deklarasi data class, fungsi yang dihasilkan compiler, dan batasannya.