news

Jumat, 12 Juni 2020

Belajar Menggunakan Data Classes di Kotlin


Pada modul ini, kita akan mempelajari sebuah fitur menarik pada Kotlin, yaitu Data Classes.
Kotlin mengenalkan konsep data class yang merupakan sebuah kelas sederhana yang bisa berperan sebagai data container. Data class adalah sebuah kelas yang tidak memiliki logika apapun dan juga tidak memiliki fungsionalitas lain selain menangani data.

Kenapa disebut dengan kelas sederhana? Seperti yang sudah kita ketahui, Kotlin memungkinkan kita untuk menulis kode dengan ringkas dan lebih efisien. 
Dalam membuat sebuah data class, kita tidak perlu menuliskan banyak kode yang seharusnya dibutuhkan untuk mengelola sebuah data. Data class mampu menyediakan beberapa fungsionalitas yang biasanya kita butuhkan untuk mengelola data hanya dengan sebuah keyword data.

  1. data class User(val name : String, val age : Int)


Hanya dengan satu baris kode di atas, kompiler akan secara otomatis menghasilkan constructortoString()equals()hashCode()copy() dan juga fungsi componentN(). Tentunya ini jauh lebih mudah dan bersih dibandingkan kita harus menuliskan banyak kode secara manual.
Beberapa hal yang perlu diperhatikan dalam membuat sebuah data class adalah:
  • Konstruktor utama pada kelas tersebut harus memiliki setidaknya satu parameter;
  • Semua konstruktor utama perlu dideklarasikan sebagai val atau var;
  • Modifier dari sebuah data class tidak bisa abstractopensealed, atau inner.

Penggunaan Data Class

Sebelum kita masuk ke dalam pembahasan tentang apa saja yang bisa data class lakukan, mari kita perhatikan dulu kode berikut:

  1. class User(val name : String, val age : Int)


Kode di atas merupakan sebuah kelas yang umumnya digunakan untuk menampung sebuah data. Kelas tersebut memiliki sebuah konstruktor yang berisi beberapa properti yang bisa kita akses, baik itu create maupun read. Selanjutnya, perhatikan juga kelas berikut:

  1. data class DataUser(val name : String, val age : Int)


Kelas hampir sama dengan sebelumnya, namun memiliki keyword data yang menandakan bahwa kelas tersebut merupakan sebuah data class. Lalu, apakah perbedaan antara keduanya? 
Untuk mengetahuinya, bukalah Intellij IDEA dan buat sebuah berkas Kotlin dengan nama DataClasses.kt. Ketikkan kedua kelas tadi di dalamnya dan buat juga fungsi main sebagai tempat di mana kita akan mencoba mengelola atau mengoperasikan kedua kelas tersebut.

  1. class User(val name : String, val age : Int)

  2.  

  3. data class DataUser(val name : String, val age : Int)

  4.  

  5. fun main(){

  6.  

  7. }


Untuk mengetahui perbedaan yang pertama, kita akan menggunakan fungsi println() untuk menampilkan 2 buah objek yang akan dibuat dari kelas User dan DataUser. Tambahkan kode berikut di dalam fungsi main():

  1. val user = User("nrohmen", 17)

  2. val dataUser = DataUser("nrohmen", 17)

  3.  

  4. println(user)

  5. println(dataUser)


Jalankan fungsi main dan lihatlah hasil yang ditampilkan pada konsol:
oo.User@4d7e1886
DataUser(name=nrohmen, age=17)
Bisa kita perhatikan, bahwa objek user menghasilkan teks oo.User@4d7e1886 dimana oo merupakan nama package tempat kelas User berada. User adalah nama dari kelas itu sendiri, dan @4d7e1886 adalah memory address dari kelas tersebut. Sedangkan, objek dataUser menghasilkan teks DataUser(name=nrohmen, age=17), yaitu nama kelas disertai dengan semua properti di dalamnya dan value dari properti tersebut. 
Dengan begitu, Anda bisa langsung mengetahui semua informasi dari dataUser hanya dengan melihat value dari properti yang ada. Mengapa demikian? 
Karena seperti yang sudah disampaikan sebelumnya,  data class akan secara otomatis menghasilkan fungsi toString() di dalamnya. 
Tanpa data class, kita perlu membuat fungsi toString() secara manual untuk mendapatkan informasi seperti yang diberikan oleh objek dataUser
Sebagai contoh, untuk menampilkan informasi yang jelas dari objek user, maka kita perlu menambahkan fungsi toString() seperti berikut:

  1. class User(val name : String, val age : Int){

  2.  

  3.     override fun toString(): String {

  4.         return "User(name=$name, age=$age)"

  5.     }

  6. }


Dengan menambahkan fungsi toString() seperti di atas, maka objek user akan bisa menghasilkan teks yang sama dengan objek dataUser. Coba jalankan kembali fungsi main().
User(name=nrohmen, age=17)
DataUser(name=nrohmen, age=17)
Selanjutnya, kelebihan lain dari data class adalah ia sudah memiliki fungsi equals() secara otomatis. Maka jika Anda ingin melakukan komparasi antara 2 buah objek, lakukanlah dengan mudah seperti contoh di bawah ini:

  1. fun main(){

  2.     val dataUser = DataUser("nrohmen", 17)

  3.     val dataUser2 = DataUser("nrohmen", 17)

  4.     val dataUser3 = DataUser("dimas", 24)

  5.  

  6.     println(dataUser.equals(dataUser2))

  7.     println(dataUser.equals(dataUser3))

  8.  

  9. }


Konsol akan langsung memberi tahu apakah kedua objek tersebut sama atau tidak ketika Anda menjalankan fungsi main():
true
false
Lain halnya jika kita melakukan komparasi pada 2 buah objek yang bukan dari data class. Kita tidak bisa mendapatkan hasil yang akurat karena konsol akan selalu menghasilkan nilai false. Sebagai contoh, perhatikanlah kode berikut:

  1. fun main(){

  2.     val user = User("nrohmen", 17)

  3.     val user2 = User("nrohmen", 17)

  4.     val user3 = User("dimas", 24)

  5.  

  6.     println(user.equals(user2))

  7.     println(user.equals(user3))

  8. }


Maka hasilnya akan sama saja, false semua:
false
false
Dan jika Anda menginginkan hasil yang akurat seperti pada data class, maka Anda perlu membuat fungsi equals() secara manual:
  1. class User(val name : String, val age : Int){
  2.  
  3.     override fun equals(other: Any?): Boolean {
  4.         if (this === other) return true
  5.         if (javaClass != other?.javaClass) return false
  6.  
  7.         other as User
  8.  
  9.         if (name != other.name) return false
  10.         if (age != other.age) return false
  11.  
  12.         return true
  13.     }
  14.  
  15.     override fun hashCode(): Int {
  16.         var result = name.hashCode()
  17.         result = 31 * result + age
  18.         return result
  19.     }
  20. }


Anda perlu menuliskan beberapa boilerplate code di atas untuk mendapatkan hasil yang sesuai. Belum lagi ketika Anda menambahkan fungsi equals(), Anda juga perlu menambahkan fungsi hashCode().

Menyalin dan Memodifikasi Data Class

Data class juga memungkinkan kita untuk menyalin sebuah objek dengan sangat mudah hanya dengan memanfaatkan fungsi copy() di dalamnya. Untuk mencobanya, buatlah objek baru dari kelas DataUser seperti berikut:

  1. fun main(){

  2.     val dataUser = DataUser("nrohmen", 17)

  3.     val dataUser2 = DataUser("nrohmen", 17)

  4.     val dataUser3 = DataUser("dimas", 24)

  5.     val dataUser4 = dataUser.copy()

  6.  

  7.     println(dataUser4)

  8. }


Jalankan fungsi main() dan seharusnya nilai dari dataUser4 akan sama dengan nilai dari dataUser. Menariknya, dengan fungsi copy() kita juga bisa memodifikasi objek tersebut dengan nilai yang baru. Sebagai contoh, kita akan mengubah nilai dari properti age menjadi 18. Cukup tuliskan kode seperti berikut:

  1. val dataUser5 = dataUser.copy(age = 18)


Maka seharusnya konsol akan menampilkan teks berikut:
DataUser(name=nrohmen, age=18)
Tanpa data class, untuk melakukan tugas seperti ini kita memerlukan sebuah instance baru untuk mengubah nilai dari suatu objek. Dengan demikian kita harus memodifikasi properti yang kita maksud. Tugas ini akan berulang dan membuat kode yang kita tulis, jauh dari paradigma clean code

Destructuring Declarations

Destructuring Declaration adalah proses memetakan objek menjadi sebuah variabel. Ini bisa dengan mudah kita lakukan pada data class. 
Dengan fungsi componentN() yang ada pada data class, kita bisa menguraikan sebuah objek menjadi beberapa properti yang dimilikinya. Sebagai contoh, kita ingin menguraikan objek dataUser:

  1. fun main(){

  2.     val dataUser = DataUser("nrohmen", 17)

  3.  

  4.     val name = dataUser.component1()

  5.     val age = dataUser.component2()

  6.  

  7.     println("My name is $name, I am $age years old")

  8. }


Maka jika kode di atas dijalankan, konsol akan menampilkan teks berikut:
My name is nrohmen, I am 17 years old
Fungsi component1() dan component2() dihasilkan sesuai dengan jumlah properti yang ada pada data class tersebut. Maka jika sebuah data class memiliki sejumlah N properti, maka secara otomatis componentN() akan dihasilkan.
Kita juga dapat membuat beberapa variabel dari objek secara langsung dengan kode seperti berikut:

  1. fun main(){

  2.     val dataUser = DataUser("nrohmen", 17)

  3.     val (name, age) = dataUser

  4.  

  5.     println("My name is $name, I am $age years old")

  6. }


Jika dijalankan, seharusnya konsol akan menampilkan hasil yang sama seperti kode sebelumnya.
Kesimpulannya, seperti aspek - aspek lain dari Kotlin, data class bertujuan untuk mengurangi jumlah kode boilerplate yang Anda tuliskan.
 Dan perlu diketahui bahwa data class tidak hanya sekedar untuk mengelola properti yang ada di dalamnya. 
Ketika mempunyai data yang sangat kompleks, kita juga bisa menerapkan sebuah behaviour di dalam data class. Contoh sederhananya, kita bisa membuat fungsi di dalam data class seperti berikut:

  1. data class DataUser(val name : String, val age : Int){

  2.     fun intro(){

  3.         println("My name is $name, I am $age years old")

  4.     }

  5. }


Dan langsung mengaksesnya dari fungsi main():

  1. fun main(){

  2.     val dataUser = DataUser("nrohmen", 23)

  3.     dataUser.intro()

  4. }