Java equals hashCode sorunlarının temel nedeni, nesnelerin hangi ölçüte göre eşit kabul edildiğinin belirsiz bırakılmasıdır. HashSet veya HashMap nesneyi çoğunlukla equals() ile hashCode() arasındaki uyumsuzluk ya da nesne koleksiyona eklendikten sonra eşitlik ölçütünün değişmesi nedeniyle bulamaz.
Önce kimlik eşitliği ile değer eşitliğini ayırmak gerekir: Aynı nesneye işaret eden iki referans kimlik bakımından eşittir; farklı nesneler aynı alan değerlerini taşıyorsa değer bakımından eşit olabilir. Bu ayrımı Java üzerinde sağlamlaştırmak için Java özel ders çalışırken özellikle nesne eşitliği ve koleksiyon davranışlarını birlikte incelemek faydalıdır.
Kimlik eşitliği ile değer eşitliği arasındaki fark nedir?
Kimlik eşitliği, iki referansın bellekte aynı nesneyi gösterip göstermediğini ifade eder. Java’da == operatörü nesnelerle kullanıldığında genellikle referansları karşılaştırır. Yani iki farklı Student nesnesi aynı isim ve aynı numaraya sahip olsa bile, bu nesneler bellekte ayrı ayrı oluşturulmuşsa == sonucu false olur.
Değer eşitliği ise nesnelerin belirli alanlarının aynı olup olmadığını sorar. Örneğin bir öğrenci için yalnızca studentNumber alanı kimlik ölçütü kabul edilebilir. Bu durumda nesnelerin farklı adreslerde bulunması önemli değildir; aynı öğrenci numarasını taşıyorlarsa mantıksal olarak eşit sayılabilirler.
Object sınıfından gelen varsayılan equals() davranışı, pratikte kimlik eşitliğine yakındır. Sınıfınız bu metodu geçersiz kılmıyorsa, aynı alan değerlerini taşıyan iki farklı nesne otomatik olarak eşit kabul edilmez.
class Student {
String name;
Student(String name) {
this.name = name;
}
public static void main(String[] args) {
Student first = new Student("Deniz");
Student second = new Student("Deniz");
System.out.println(first == second);
System.out.println(first.equals(second));
}
}
Bu kodun beklenen çıktısı şöyledir:
false
false
Burada iki nesnenin de name alanı "Deniz" değerindedir. Buna rağmen hem == hem de varsayılan equals() iki farklı nesne referansını karşılaştırdığı için sonuç false olur. Değer eşitliği isteniyorsa sınıfın hangi alanlara göre eşit sayılacağını açıkça tanımlamak gerekir.
Bu ayrım HashSet ve HashMap gibi hash tabanlı koleksiyonlarda kritik hâle gelir. Koleksiyon, bir nesneyi yalnızca “alanları benziyor mu?” diye aramaz; nesnenin eşitlik mantığını ve hash kodunu birlikte kullanır. Bu nedenle yalnızca ekranda aynı görünen iki nesnenin koleksiyon açısından eşit olması garanti değildir.
equals() ve hashCode() sözleşmesi hangi kurallara dayanır?

equals() metodu, geliştiricinin nesne eşitliğini tutarlı biçimde tanımlamasını sağlayan bir sözleşmeye uyar. Bu sözleşmenin dört temel kuralı vardır. Ayrıca hash tabanlı koleksiyonlar için ayrı ve vazgeçilmez bir koşul bulunur: Eşit kabul edilen iki nesne aynı hash kodunu üretmelidir.
| Kural adı | Anlamı | İhlal edilirse sonucu |
|---|---|---|
| Reflexive | Bir nesne kendisine eşit olmalıdır: x.equals(x) |
Nesne kendi içinde bile tutarsız davranır; koleksiyon aramaları güvenilmezleşir. |
| Symmetric | x.equals(y) sonucu ile y.equals(x) sonucu aynı olmalıdır. |
Karşılaştırmanın yönüne göre farklı sonuçlar alınır. |
| Transitive | x, y’ye ve y, z’ye eşitse x, z’ye de eşit olmalıdır. |
Üç nesneli karşılaştırmalarda çelişkili eşitlik oluşur. |
| Consistent | Karşılaştırılan alanlar değişmediği sürece sonuç aynı kalmalıdır. | Aynı nesne için farklı zamanlarda farklı sonuçlar alınabilir. |
Örneğin Student sınıfında eşitlik ölçütü öğrenci numarasıysa, iki öğrencinin isimlerinin farklı olması sonucu değiştirmemelidir. Bunun tersine, bir sınıf bir karşılaştırmada öğrenci numarasını, başka bir karşılaştırmada e-posta adresini kullanırsa simetri veya tutarlılık bozulabilir.
Hash kuralının mantığı tek yönlüdür: Eşit nesneler aynı hash kodunu üretmelidir. Ancak aynı hash koduna sahip nesnelerin mutlaka eşit olması gerekmez. Hash kodu, nesnenin yerleştirileceği kovayı bulmaya yardımcı olur; kesin eşitlik kontrolünü ise equals() tamamlar. Eşit nesneler farklı hash kodlarına giderse HashSet arama sırasında doğru kovaya bakamaz ve nesne mevcut olduğu hâlde bulunamıyormuş gibi görünür.
Bir sınıfta eşitlik mantığını kontrol ederken şu kısa liste kullanılabilir:
- Eşitliğin hangi alanlara göre belirlendiğini tek cümleyle açıklayabiliyor musunuz?
- Bir nesne kendisiyle karşılaştırıldığında
truesonucu alıyor mu? - Karşılaştırmayı ters çevirdiğinizde sonuç değişiyor mu?
- Üç nesneli karşılaştırmada eşitlik zinciri bozuluyor mu?
- Karşılaştırılan alanlar değişmeden
equals()sonucu sabit kalıyor mu? equals()içinde kullanılan her alan,hashCode()hesaplamasında da aynı mantıkla ele alınıyor mu?
Yalnızca equals() yazılırsa HashSet nesneyi neden bulamaz?
Bir sınıfta equals() metodunu e-posta alanına göre yeniden yazıp hashCode() metodunu olduğu gibi bırakırsanız, nesneler mantıken eşit görünse bile HashSet içinde bulunamayabilir. Çünkü HashSet, arama sırasında önce hash kodunu kullanarak bakacağı kovayı seçer; ancak eşitliği o kovadaki adaylar üzerinde equals() ile kontrol eder.
Aşağıdaki hatalı sınıfta iki farklı User nesnesinin e-posta adresi aynıdır. Bu nedenle equals() sonucu true olur. Fakat hashCode() override edilmediği için nesneler varsayılan, kimliğe dayalı hash kodlarını kullanır.
import java.util.HashSet;
public class Main {
static class User {
String email;
User(String email) { this.email = email; }
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof User)) return false;
User other = (User) obj;
return email.equals(other.email);
}
}
public static void main(String[] args) {
User first = new User("[email protected]");
User probe = new User("[email protected]");
HashSet<User> users = new HashSet<>();
System.out.println(users.add(first));
System.out.println(first.equals(probe));
System.out.println(users.contains(probe));
}
}
Bu örnekte beklenen çıktı şöyledir:
true
true
false
İlk satır, first nesnesinin kümeye başarıyla eklendiğini gösterir. İkinci satırda iki nesnenin e-posta değerleri aynı olduğu için equals() true döndürür. Üçüncü satırda ise contains(probe) beklenmedik biçimde false olabilir. Bunun nedeni, probe için hesaplanan hash kodunun first nesnesini eklerken kullanılan hash koduyla aynı kovayı işaret etmemesidir. Hash çakışması oluşursa sonuç farklılaşabilir; sorun, eşit nesnelerin eşleşebilir hash kodu üretmemesidir.
Akış basitçe şu şekilde işler:
firstiçinhashCode()çağrılır ve bir hash kovası seçilir.firstseçilen kovaya yerleştirilir.probeilecontains()çağrıldığında yineprobe.hashCode()hesaplanır.- Farklı bir kova seçilirse,
first.equals(probe)hiç çağrılmadan arama başarısız olur.
equals() ve hashCode() birlikte yazıldığında sonuç nasıl değişir?

Doğru uygulamada aynı eşitlik ölçütü iki metoda da yansıtılır: Eşitliği belirleyen alan email ise hem equals() hem de hashCode() yalnızca bu alanı kullanmalıdır. Böylece aynı e-posta adresine sahip iki farklı nesne aynı hash kovasına yönlendirilir ve ardından equals() karşılaştırması doğru sonucu verir.
import java.util.HashSet;
import java.util.Objects;
public class Main {
static class User {
String email;
User(String email) { this.email = email; }
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof User)) return false;
User other = (User) obj;
return Objects.equals(email, other.email);
}
@Override
public int hashCode() {
return Objects.hash(email);
}
}
public static void main(String[] args) {
User first = new User("[email protected]");
User probe = new User("[email protected]");
HashSet<User> users = new HashSet<>();
System.out.println(users.add(first));
System.out.println(users.add(probe));
System.out.println(users.contains(probe));
}
}
Beklenen çıktı:
true
false
true
first kümeye ilk kez eklendiği için sonuç true olur. probe farklı bir nesne olmasına rağmen aynı e-posta değerini taşıdığı için equals() bakımından eşittir ve aynı hash kodunu üretir. Bu nedenle add(probe) yeni bir eleman eklemez, false döndürür. Aynı nedenle contains(probe) sonucu true olur.
- Yalnızca equals(): Mantıksal eşitlik doğru görünür; ancak nesneler farklı hash kovalarına gidebilir.
- equals() ve hashCode(): Aynı ölçüt kullanıldığı için
HashSetaraması ve yinelenen eleman kontrolü beklenen şekilde çalışır.
Aynı sözleşme HashMap anahtarları için de geçerlidir. Bir User nesnesi anahtar olarak kullanılıp daha sonra aynı e-posta adresine sahip yeni bir nesneyle get() çağrılırsa, hatalı hashCode() uygulaması null veya beklenmeyen bir sonuç doğurabilir. Bu nedenle sorun teşhisinde yalnızca equals() sonucuna bakmak yeterli değildir.
- Eşitlik testi: Aynı değeri taşıyan iki farklı nesnede
equals()sonucununtrueolduğunu kontrol edin. - Hash kodu testi: Eşit nesnelerin
hashCode()değerlerinin aynı olduğunu doğrulayın. - Koleksiyon kontrolü: Nesneyi eklemeden ve ekledikten sonra
contains(),add()veyaget()sonuçlarını ayrı ayrı inceleyin. - Değişebilir alan incelemesi: Eşitlikte kullanılan alanların koleksiyona ekleme sonrasında değişip değişmediğini kontrol edin.
Bu mantığı pekiştirmek için Java bilgi testi üzerinde özellikle nesne eşitliği, koleksiyonlar ve metot override etme konularındaki soruları çözebilirsiniz.
IDE otomatik üretimi ve test zihniyeti hataları nasıl yakalar?
IDE’lerin equals() ve hashCode() metotlarını otomatik üretmesi, yazım hatalarını azaltır; ancak eşitlik kararını sizin yerinize vermez. IntelliJ IDEA, Eclipse veya benzeri geliştirme ortamları genellikle hangi alanların kullanılacağını sorar ve oluşturduğu kodu seçtiğiniz alanlara göre düzenler. Bu nedenle otomatik üretilen kod teknik olarak doğru görünse bile, seçilen alanlar iş kuralını yansıtmıyorsa uygulama hatalı davranabilir.
Örneğin bir User nesnesinde email alanı gerçekten benzersiz ve değişmez bir kimlik olarak kabul ediliyorsa yalnızca bu alanı kullanmak mantıklı olabilir. Fakat IDE üretimi sırasında name, department, createdAt ve geçici durum bilgileri de seçilirse aynı kişiye ait iki nesne eşit kabul edilmeyebilir. Kullanıcının adı veya departmanı değiştiğinde, daha önce aynı kabul edilen nesnenin eşitlik sonucu da değişebilir.
Tersine, yalnızca email alanını seçmek de her projede otomatik olarak doğru karar değildir. Bazı sistemlerde email değiştirilebilir bir iletişim bilgisi olabilir; gerçek kimlik ise veritabanı kimliği, UUID veya başka bir alanla temsil edilebilir. Buradaki temel soru “IDE hangi alanları seçti?” değil, nesneler hangi iş kuralına göre aynı kabul ediliyor? sorusudur.
Otomatik üretimde alan seçerken nelere bakılmalı?
- Kimlik alanı: Nesnenin yaşamı boyunca değişmeyen ve onu diğerlerinden ayıran alanlar tercih edilmelidir.
- Değişebilir durum: Aktiflik, puan, bölüm, adres veya oturum durumu gibi alanlar çoğu zaman eşitlik için uygun değildir.
- Oluşturulma zamanı: Teknik olarak farklı zamanlarda oluşturulan iki nesnenin aynı iş nesnesini temsil edip etmediği ayrıca değerlendirilmelidir.
- Alanların birlikte anlamı: Bazen tek bir email yerine ülke kodu ve kullanıcı adı gibi alanların birleşimi kimlik oluşturabilir.
- İki metot arasındaki uyum:
equals()içinde kullanılan alanlarlahashCode()içinde kullanılan alanlar aynı mantığı takip etmelidir.
IDE’nin yaptığı işlem yalnızca kod üretmektir. Alanların değişmez olup olmadığını, veritabanında benzersiz tutulup tutulmadığını veya iş açısından gerçekten kimlik sayılıp sayılmayacağını kendiliğinden garanti etmez. Bu yüzden otomatik üretimden sonra kodu okumak ve alan seçimini bilinçli biçimde gözden geçirmek gerekir. Java’da hata ayıklama çalışırken bu kontrol, sözdizimini incelemek kadar önemlidir. Düzenli ve uygulamalı bir öğrenme süreci için online yazılım eğitimi içinde bu tür nesne davranışlarını küçük deneylerle incelemek, ezberden daha kalıcı bir yöntem sağlar.
JUnit benzeri test zihniyeti nasıl kurulmalı?
Buradaki amaç belirli bir test kütüphanesini öğretmek değil, kodu yazarken hangi davranışların mutlaka sınanması gerektiğini düşünmektir. Örneğin assertEquals(), iki nesnenin değer bakımından eşit olup olmadığını; assertTrue(set.contains(...)) ise eşitlik ve hash davranışının koleksiyon üzerinde birlikte doğru çalışıp çalışmadığını sorgular.
User first = new User("[email protected]", "Ada");
User second = new User("[email protected]", "Ada");
assertEquals(first, second);
assertEquals(first.hashCode(), second.hashCode());
Set<User> users = new HashSet<>();
users.add(first);
assertTrue(users.contains(second));
Bu örnekte first ve second farklı nesnelerdir; fakat eşitlik kuralına göre aynı kullanıcıyı temsil ediyorlarsa HashSet içindeki arama başarılı olmalıdır. Test başarısızsa yalnızca contains() satırına bakmak yerine, eşit kabul edilen nesnelerin hash kodlarının da aynı olup olmadığı incelenmelidir.
Uygulanabilir bir kontrol listesi şu şekilde kurulabilir:
- Bir nesne kendisiyle karşılaştırıldığında
equals()sonucutruedönüyor mu? - Aynı değerlere sahip fakat farklı referanslar olan iki nesne eşit kabul ediliyor mu?
- Kimlik alanı farklı olan iki nesne yanlışlıkla eşit kabul ediliyor mu?
- Bir nesne
HashSetiçine eklendikten sonra eşit değer taşıyan başka nesneylecontains()başarılı oluyor mu? - Bir nesne
HashMapanahtarı olarak kullanıldığında eşit anahtarlaget()beklenen değeri getiriyor mu? equals()sonucutrueolan iki nesnenin hash kodları aynı mı?
Bu testler yalnızca normal senaryoyu değil, sınır durumlarını da kapsamalıdır. Özellikle null değerler, farklı sınıftan nesneler ve eşitlikte kullanılan alanlardan birinin sonradan değiştirilmesi ayrıca düşünülmelidir.
Mutable alan değişirse koleksiyondaki nesne neden kaybolur?
equals() ve hashCode() içinde kullanılan bir alan, nesne HashSet içine eklendikten sonra değiştirilirse koleksiyon nesneyi fiziksel olarak silmez; fakat arama mekanizması onu bulamayabilir. Bu durum, nesnenin “kaybolmuş” gibi görünmesine neden olur.
Örneğin email alanı eşitlikte kullanılıyorsa süreç adım adım şöyledir:
usernesnesi"[email protected]"email değeriyle oluşturulur.- Nesne
HashSetiçine eklenirken mevcut email değerine göre bir hash kodu hesaplanır. - Bu hash kodu, nesnenin yerleştirileceği kovayı belirler.
- Daha sonra email değeri
"[email protected]"olarak değiştirilir. - Aynı nesne için artık farklı bir hash kodu hesaplanabilir.
set.contains(user)çağrısı yeni hash kodunun gösterdiği kovaya bakar.- Nesne hâlâ eski kovada durduğu için arama
falsedönebilir.
Buradaki sorun, HashSet nesneyi yeniden yerleştirecek şekilde kendini güncellemez. Koleksiyon, nesne eklenirken hesaplanan yerleşim mantığına güvenir. Nesnenin eşitlikte kullanılan durumu sonradan değişirse, koleksiyonun iç düzeniyle nesnenin güncel hash kodu birbirinden kopar.
Aynı risk HashMap anahtarlarında da vardır. Email alanı değiştirilen anahtarla map.get(user) çağrıldığında, harita yeni hash koduna göre arama yapar; ancak değer eski kovada bulunuyor olabilir. Bu nedenle get() sonucu null dönebilir. Bu sonuç, haritada değer kesinlikle yok anlamına gelmeyebilir; anahtarın koleksiyona eklendikten sonra değiştirildiğini de düşündürmelidir.
Bu risk nasıl yönetilir?
- Eşitlikte kullanılan alanı nesne
HashSetveyaHashMapiçinde bulunduğu sürece değiştirmemek. - Değişiklik zorunluysa önce
remove, sonra alanı değiştirme ve ardındanaddişlemlerini değerlendirmek. - Mümkünse değişmez bir kimlik alanı kullanmak.
- Email gibi bir alanın gerçekten değişmez kimlik olup olmadığını veri modeline göre kararlaştırmak.
- Mutable nesneleri anahtar olarak kullanmak yerine, değişmez anahtar değerleriyle çalışmak.
Örneğin email güncellenecekse önce nesnenin eski hâliyle set içinden çıkarılması, sonra email değerinin değiştirilmesi ve güncel hâlin yeniden eklenmesi gerekebilir. Ancak bu yaklaşımın güvenli olması için nesneye erişimin, değişiklikten önceki eşitlik değerleriyle yapılabilmesi gerekir. En sağlam karar çoğu durumda değişmeyen bir kimliği eşitlik temeline almaktır; fakat email’in değişmez sayılıp sayılmayacağı her alan modelinde aynı değildir.
Sık Sorulan Sorular
equals() true döndüğü hâlde HashSet.contains() neden false dönebilir?
Nesnenin equals() veya hashCode() hesabında kullanılan alanlardan biri sonradan değişmiş olabilir. Nesne eski kovada kalırken arama yeni hash koduna göre yapılır. Ayrıca eşit nesneler için farklı hash kodları üretilmesi de aynı sonuca yol açabilir.
HashMap anahtarında equals() ve hashCode() neden birlikte override edilmelidir?
HashMap önce hash koduyla ilgili kovayı bulur, ardından equals() ile anahtarı karşılaştırır. Yalnızca equals() override edilirse eşit anahtarlar farklı kovalara düşebilir ve get() beklenen değeri bulamayabilir.
equals() ve hashCode() içinde hangi alanlar kullanılmalıdır?
Nesnenin iş açısından kimliğini temsil eden, mümkünse benzersiz ve değişmez alanlar kullanılmalıdır. Ad, bölüm, oluşturulma zamanı veya geçici durum gibi değişebilen alanlar ancak eşitlik tanımının gerçekten parçasıysa seçilmelidir.
HashSet’e eklenen bir nesnenin email alanı değiştirilirse nasıl düzeltilir?
Nesne önce eski hâliyle remove() edilip, email değiştirildikten sonra add() ile yeniden eklenebilir. Daha güvenli tasarım ise eşitlikte kullanılan alanı değişmez tutmak veya değişmeyen bir kimlik alanını temel almaktır.
Doğru alan seçimi ve küçük davranış testleri, HashSet ile HashMap kaynaklı zor hataları daha oluşmadan görünür hâle getirir.