Katakanlah saya memiliki sebuah List
dari angka seperti berikut
val list = List(1, 2, 3, 4, 2, 3, 4)
Jika saya ingin membuatnya tidak berisi angka berulang, saya bisa panggil method distinct
dari List
tersebut.
scala> val list = List(1, 2, 3, 4, 2, 3, 4)
list: List[Int] = List(1, 2, 3, 4, 2, 3, 4)
scala> list.distinct
res1: List[Int] = List(1, 2, 3, 4)
Nah bagaimana jika isi List
adalah objek dari kelas buatan sendiri?
Pertama, mari kita coba gunakan case class
. Katakanlah saya butuh menyimpan dua buah nilai yaitu posisi: Int
dan asamAmino: String
yang menyatakan sebuah mutasi gen seperti berikut.
case class M1(val posisi: Int, val asamAmino: String)
Mari kita uji apakah distinct
bisa bekerja sesuai harapan.
scala> case class M1(val posisi: Int, val asamAmino: String)
defined class M1
scala> val list = List(M1(1, "a"), M1(2, "b"), M1(1, "c"), M1(3, "b"), M1(1, "a"))
list: List[M1] = List(M1(1,a), M1(2,b), M1(1,c), M1(3,b), M1(1,a))
scala> list.distinct
res2: List[M1] = List(M1(1,a), M1(2,b), M1(1,c), M1(3,b))
Terlihat masih oke.. Bagaimana kalau pakai class
biasa?
scala> class M2(val posisi: Int, val asamAmino: String) {
| override def toString() = "%d%s".format(posisi, asamAmino)
| }
defined class M2
scala> val list = List(new M2(1, "a"), new M2(2, "b"), new M2(1, "c"), new M2(3, "b"), new M2(1, "a"))
list: List[M2] = List(1a, 2b, 1c, 3b, 1a)
scala> list.distinct
res3: List[M2] = List(1a, 2b, 1c, 3b, 1a)
Wah tidak bekerja sesuai harapan ternyata π Mutasi 1a
masih berjumlah ganda. Mungkin karena si Scala atau Java tidak tahu cara mengecek dengan benar apakah dua buah objek M2
itu bernilai sama atau tidak. Kalau yang dibandingkan hanyalah alamat memori, tentu saja akan berbeda semua.
Setelah ngubek2 internet, akhirnya saya mendapatkan sebuah petunjuk di StackOverflow.com. Pada saat pengecekan kesamaan nilai, Scala menggunakan dua buah method untuk mencari tahu apakah dua buah objek bernilai sama atau tidak. Dua method tersebut adalah hashCode
dan equals
. Nilai hashCode
akan dibandingkan terlebih dahulu dan jika sama, maka method equals
akan dipanggil agar si objek membandingkan sendiri dengan objek lain apakah mereka bernilai sama atau tidak.
Berbekal dari informasi tersebut, kita bisa ubah si kelas M2
menjadi seperti berikut.
class M3(val posisi: Int, val asamAmino: String) {
override def toString() = "%d%s".format(posisi, asamAmino)
override def hashCode = posisi
override def equals(other: Any) =
if (other.isInstanceOf[M3]) {
val m = other.asInstanceOf[M3]
m.posisi == posisi && m.asamAmino == asamAmino
} else false
}
Untuk hashCode
, agar sederhana saya samakan saja nilainya dengan nilai posisi
. Pada saat pemangilan equals
, nilai posisi
dan asamAmino
akan dibandingkan untuk memutuskan apakah dua buah mutasi bernilai sama atau tidak.
Mari kita coba lagi..
scala> val list = List(new M3(1, "a"), new M3(2, "b"), new M3(1, "c"), new M3(3, "b"), new M3(1, "a"))
list: List[M3] = List(1a, 2b, 1c, 3b, 1a)
scala> list.distinct
res0: List[M3] = List(1a, 2b, 1c, 3b)
Hore berhasil!
Mengapa saya ngga memakai case class
saja sehingga tidak perlu repot2 mengimplementasikan hashCode
dan equals
? Karena dalam kasus yang saya hadapi, kelas mutasi ini berada dalam wilayah bahasa Java! Yap, saya sedang mencampur2 kode Java dan Scala.
Mari kita ubah si kelas mutasi menjadi bahasa Java lalu digunakan dari Scala.
public class M4 {
private int posisi;
private String asamAmino;
public M4(int posisi, String asamAmino) {
this.posisi = posisi;
this.asamAmino = asamAmino;
}
public int getPosisi() {
return posisi;
}
public String getAsamAmino() {
return asamAmino;
}
@Override
public int hashCode() {
return posisi;
}
@Override
public boolean equals(Object other) {
if (other instanceof M4) {
M4 m = (M4)other;
return posisi == m.getPosisi() && asamAmino.equals(m.getAsamAmino());
}
return false;
}
@Override
public String toString() {
return posisi + asamAmino;
}
}
Wah kodenya jadi bengkak ya? π Silakan lihat kode lengkap dapat dilihat di https://gist.github.com/fajran/5718827