Monday, May 21, 2012

Java equals ve hashCode Metodlarını kullanmak


Bugün evde işte karşılaştığım bir problemi nasıl çözebileceğimi düşünürken ürettiğim çözümü paylaşmak istiyorum.

Sorunum şu:
  1. Elimde bir liste içerisinde nesneler var. 
  2. Liste içerisinde bir eleman birden çok kez tekrar edebiliyor.
  3. Bana her nesneden yalnızca 1 tane içeren bir liste gerekiyor.
  4. Nesnelerin birbirine eşit olma durumlarını tekil id değerlerine bakarak ayırt edebiliyorum.
Eğer ki Liste içindeki elemanlar Integer, String gibi tiplerde olsaydı liste içerisindeki elemanları HashSet içerisine atarak tekilleştirebilirdim. HashSet bir elemandan sadece 1 tane barındırmayı garanti ediyor. Örnek kod aşağıdaki gibidir. Liste içerisinde 6 eleman yer alırken HashSet içerisinde ise tekil olan "String1", "String2" ve "String3" elamanları yer alıyor.

List<String> stringList = new ArrayList<String>();
stringList.add("String1");
stringList.add("String2");
stringList.add("String2");
stringList.add("String3");
stringList.add("String3");
stringList.add("String3");
  
Set<String> stringSet = new HashSet<String>();
stringSet.addAll(stringList);
  
System.out.println("list size: " + stringList.size());
System.out.println("hash set size: " + stringSet.size());
//derleyici çıktısı
//list size: 6
//hash set size: 3 

Kendi sınıfımda verileri tekilleştirmek için for döngüsü içinde bir şeyler yapmak yerine işi java'ya bırakmaya karar verdim. HashSet'in elemanların tekilliğinin kontrolü için hashCode metodunun ürettiği değeri kontrtol ettiğini biliyorum. hashCode metodu için java dökümantasyonu şöyle diyor:

This method returns the hash code value for the object on which this method is invoked. This method returns the hash code value as an integer and is supported for the benefit of hashing based collection classes such as Hashtable, HashMap, HashSet etc. This method must be overridden in every class that overrides the equals method.

Bir anlaşma olarak equals metodunun override edildiği her sınıftta hashCode metodu da override edilmelidir.
  • Sadece equals metodunu override edersem nesnelerim equals metodu ile eşit görünse de hash kod  değerleri farklı olacak.
  • Sadece hashCode metodunu override edersem nesnelerim aynı hash code değerine sahip olmasına rağmen equals metodu bu 2 nesnesin farklı olduğunu söyleyecek.
Java dökümantasyonu equals metodu ile aynı olan 2 nesnenin aynı hash kod değerine sahip olması gerektiğini söylüyor. Daha ayrıntılı bilgi için yazının sonundaki kaynaklar  bölümünde bulunan linke göz atabilirsiniz. Yararlı bir makale olmuş.

equals metodunu da override etmem gerektiğini düşünüyorum. Böylece  equals metodu ile nesnelerimin eşitlik durumlarını kontrol edebilirim.

Sorunu tekrar edebilmek için aşağıdaki gibi bir sınıf yazıyorum.

package org.guneriu.test.models;

public class Person {

 private Long identityNumber;
 private String name;

 public Long getIdentityNumber() {
  return identityNumber;
 }

 public void setIdentityNumber(Long identityNumber) {
  this.identityNumber = identityNumber;
 }

 public String getName() {
  return name;
 }

 public void setName(String name) {
  this.name = name;
 }

 @Override
 public int hashCode() {
  final int prime = 31;
  int result = 1;
  result = prime * result + ((identityNumber == null) ? 0 : identityNumber.hashCode());
  return result;
 }

 @Override
 public boolean equals(Object obj) {
  if ( this == obj )
   return true;
  if ( obj == null )
   return false;
  if ( getClass() != obj.getClass() )
   return false;
  Person other = (Person) obj;
  if ( identityNumber == null ) {
   if ( other.identityNumber != null )
    return false;
  } else if ( !identityNumber.equals(other.identityNumber) )
   return false;
  return true;
 }

}


Person sınıfının identityNumber alanı benim için tekil bir değer (T.C. no diyelim). HashCode üretirken kullandığımız alanın değişmez (immutable) bir değer olması önemlidir. Aksi takdirde hash tabanlı koleksiyonlarda koleksiyon içerisinden bulunan bir nesnenin hashCode değeri değişirse beklenmedik sorunlar ortaya çıkabilir.

equals ve hashCode metodlarını oturup kendim yazabilirdim. Ancak bu işi Eclipse'e bıraktım. equals ve hashCode metodlarının override edilmesi ile ilgili Java'nın sunduğu bazı tavsiyeler var. Eclipse bunları benim için yerine getirerek güvenliğimi arttırıyor. Eclipse source menü'de (alt + shift + s) "Generate hashCode() and equals()" özelliğini kullandım. Basitçe algoritmada sınıf içerisinde hangi değerleri kullanmak istediğimi sordu ve metodları oluşturdu.

Şimdi çalışıp çalışmadığına bakmak için 4 tane Person nesnesi oluşturuyorum. Bunlarda 2 tanesi aynı identityNumber değerine sahip. Bana tekil olan 3 tane Person nesnesi gerekiyor.

package org.guneriu.test;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.guneriu.test.models.Person;

public class PersonTest {


 public static void main(String[] args) {
  Person firstPerson = new Person(123L);
  Person secondPerson= new Person(1234L);
  Person thirdPerson= new Person(12345L);
  Person fourthPerson= new Person(123L);
  
  
  List<Person> personList = new ArrayList<Person>();
  personList.add(firstPerson);
  personList.add(secondPerson);
  personList.add(thirdPerson);
  personList.add(fourthPerson);
  
  Set<Person> personSet = new HashSet<Person>();
  personSet.addAll(personList);
  
  System.out.println("list size: " + personList.size());
  System.out.println("hash set size: " + personSet.size());
  //equals metodunu da kullanarak eşitlik durumuna bakabiliyorum
  System.out.println("is firstPerson equals fourthPerson: " + firstPerson.equals(fourthPerson));
  System.out.println("is firstPerson equals secondPerson: " + firstPerson.equals(secondPerson));
  
  //derleyici çıktısı
  //list size: 4
  //hash set size: 3
  //is firstPerson equals fourthPerson: true
  //is firstPerson equals secondPerson: false
 }
 
}

Kendi algoritmamı yazmak yerine işi Java'ya bırakarak hem zaman kazanıyorum hem ortaya daha temiz bir kod çıkıyor.
Kaynaklar: http://www.technofundo.com/tech/java/equalhash.html