Overriding hashCode()
Selam dostum yine ben 😎 ve yeni ünitenin ikinci yazısıyla devam ediyoruz. Bu yazıda Effective Java kitabının ikinci ünitesi olan Methods Common to All Objects'in ikinci maddesini ele alacağız.
Bu madde içerisinde Object
sınıfı içerisinde yer alan hashcode()
metodunun nasıl override edileceğini, hangi koşullarda override edilmesi gerektiğini ve dikkat etmemiz gereken kritik hatalara değineceğim.
Object
sınıfımızı bir ziyaret edelim ne varmış bu hashCode()
içerisinde.
/**
* Returns a hash code value for the object. This method is
* supported for the benefit of hash tables such as those provided by
* {@link java.util.HashMap}.
*/
public native int hashCode();
Object
sınıfı içerisindeki bu metoda baktığımda herhangi bir implementasyonunun olmadığını görüyorum. Peki ne yapıyor bu metot? Aslında kendi sınıfım için bir hash code üretmek istediğim durumda bu metodu override edip nesnem için bir hash code üretmem gerekiyor. Peki hashCode()
metodunu ne zaman kullanmam gerekiyor. equals()
metodunun override edilmesi durumunda muhakkak hashCode
'unda override edilmesi gerekiyor. Peki ne alaka, neden böyle bir gereksinim var? Diyorsan merak etme daha detaylı inceleyeceğim. İncelemeye geçmeden önce Overriding equals() yazıma göz atmak isteyebilirsin.
equals()
metodu gibi hashCode()
metodunun da kendine ait sözleşmesi mevcut hashCode()
implementasyonu yaparken bu maddeleri ihmal etmemem gerekiyor. Nedenini sözleşme maddelerinden sonra açıklayacağım.
- Bir nesne içerisinde override ettiğim
hashCode()
metodu her çağrılmada aynı değeri üretmelidir. Bu durumequals()
içerisindeki kontrollerin değişmediği sürece sağlanması gerekir. equals()
metoduna göre iki nesne birbirine eşitse her iki nesnenindehashCode()
metodu aynı tam sayı değerini üretmelidir.- İkinci maddenin aksine
equals()
metoduna göre iki nesne birbirine eşit değilse, her iki nesneye aithashCode()
metodunun farklı tam sayı üretme gerekliliği yoktur. Fakat farklı sayılar üretmesihashTable
performansını arttırır.
Tam bu aşamada hash code'un ne olduğundan bahsetmek gerekirse HashMap
, HashSet
, HashTable
gibi veri yapıları bahsi geçen hashCode()
ile hash code hesaplarken nesnenin o anki bellekte bulunduğu konumu referans alır.
Hızlıca bir kural ihlali ile devam edeyim. Eğer equals()
metodunu implement etmiş fakat hashCode()
metoduna hiç dokunmamış isem; equals()
metoduna göre eşit olan nesnelerimi HashMap
, HashSet
, HashTable
gibi veri yapılarında kullandığımda birbirinden farklı iki nesne olarak görecektir. Bu durum benim ikinci maddemi ihlal ettiğimi gösterir.
Hemen bir örnek ile pekiştirmeni sağlayayım:
public class PhoneNumber {
private int areaCode;
private int prefix;
private int lineNumber;
public PhoneNumber(int areaCode, int prefix, int lineNumber) {
this.areaCode = areaCode;
this.prefix = prefix;
this.lineNumber = lineNumber;
}
@Override
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof PhoneNumber))
return false;
PhoneNumber pn = (PhoneNumber) o;
return pn.lineNumber == lineNumber &&
pn.prefix == prefix &&
pn.areaCode == areaCode;
}
// No hashCode() method!
}
Yukarıdaki sınıfımız equals
metodu anlamında gayet tamam gibi duruyor. Peki ya hashCode()
olmadığına göre burada ne gibi bir sorun var? Sınıf için instance'lar üretmeyi deneyelim...
Map<PhoneNumber, String> map = new HashMap<>();
map.put(new PhoneNumber(707, 867, 5309), "Jenny");
Tamamadır. Key = new PhoneNumber(707, 867, 5309), Value = "Jenny" olacak şekilde elimde bir map entrysi mevcut değil mi?
map.get(new PhoneNumber(707, 867, 5309));
Veee "Jenny" cevabını görürüz... Demek isterdim ama malesef null
dönecektir. equals()
metodunda bu iki nesne birbirine eşittir new PhoneNumber(707, 867, 5309).equals(new PhoneNumber(707, 867, 5309))
kontrolü true
değeri dönecektir. equals()
mantıksal değer kontrolü yaparken hashCode()
'un bellek referansına baktığı unutulmamalıdır.
Bu sorunu çözmenin yolu tabii ki yazıda incelediğimiz konu ile aynı 😊 Peki hashCode()
metodunu sözleşme maddelerine uyacak şekilde nasıl yazabiliriz. Bu aşamada sözleşme maddelerine uymak pek zor değil fakat birbirinden farklı hash code üretmek epey bir zordur. Hatta aşağıdaki örnek sözleşmelere uyar fakat çok kötü bir hash code implementasyonudur.
// The worst possible legal hashCode implementation - never use!
@Override public int hashCode() { return 53; }
✅ Nesnemizin hashCode()
metodu her çağrıldığında aynı değeri mi dönüyor?
✅ equals()
içerisinde kontrol edilen eşit nesnelerin hash code’ları aynı mı?
✅ equals()
içerisinde kontrol edilen eşit olmayan nesnelerin hash codeları aynı olmak zorunda değil.
Gördüğün gibi sözleşmenin her bir maddesi gerçekleştiriliyor. Fakat iyi bir hash code ürettim diyebilmek için sahip olduğu değerler büyük oranda birbirinden farklı olmalıdır. Farklı olmadığı durumda büyük performans kayıplarına neden olur. Daha önce sözleşmenin 3. maddesinde bahsettiğim HashTable
'ı LinkedList
gibi çalıştırarak 4 kat yavaş çalışmasına neden olmaktadır. Örnek birkaç hashCode()
implementasyonu şu şekilde;
- Standart
hashCode()
implementasyonu (en iyi performans):
@Override
public int hashCode() {
int hash = 7;
hash = 31 * hash + (int) id;
hash = 31 * hash + (name == null ? 0 : name.hashCode());
hash = 31 * hash + (email == null ? 0 : email.hashCode());
return hash;
}
- Nesnemin özelliklerini de kullanabilrim:
@Override
public int hashCode() {
return (int) id * name.hashCode() * email.hashCode();
}
Bunların alternatifi olarak @AutoValue
annotation'ını kullanabilirsiniz. Sizler için otomatik değer üretecektir.
equals()
metodunu kendi sınıfı için override eden arkadaşlarımın hashCode()
konusunda da aynı hassasiyeti göstermeleri gerektiği konusunda bence hem fikirizdir değil mi?
Sonraki yazıda görüşmek üzere 🖖🏼