1. Giriş
Obyektləri necə müqayisə edirik
Java-da obyektlər — sadəcə məlumat deyil, hər birinin yaddaşda öz ünvanı var. == operatoru “bu eyni qutudurmu?” sualına cavab verir, yəni məzmunu deyil, istinadları (ünvanları) müqayisə edir. equals metodu isə məhz məzmunun müqayisəsi üçün nəzərdə tutulub. Susmaya görə, əgər onu yenidən yazmasanız, equals == kimi davranır.
Person p1 = new Person("Ivan", 20);
Person p2 = new Person("Ivan", 20);
System.out.println(p1 == p2); // false — bunlar yaddaşda fərqli obyektlərdir!
İki fərqli obyektin məlumatlara görə bərabər sayılması üçün (məsələn, bütün əhəmiyyətli sahələr üst-üstə düşürsə) equals metodunu yenidən yazmaq lazımdır. Və əgər obyektləri heş-kolleksiyalarda istifadə etməyi planlaşdırırsınızsa, onunla birlikdə hashCode metodunu da düzgün şəkildə yenidən yazmağınız mütləqdir.
class Person {
String name;
int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true; // bu eyni obyektdir
if (o == null || getClass() != o.getClass()) return false; // sinfi yoxlayırıq
Person person = (Person) o; // lazımi tipə çeviririk
return age == person.age && name.equals(person.name); // sahələri müqayisə edirik
}
@Override
public int hashCode() {
return Objects.hash(name, age); // HashSet/HashMap ilə işləməsi üçün
}
}
Belə yenidən yazma HashSet/HashMap ilə düzgün işləmək üçün kritikdir: equals və hashCode olmadan, kolleksiyalar məlumatlara görə eyni olan obyektləri belə fərqli hesab edəcəklər.
equals daxilində ekvivalentlik münasibəti sizin tələblərinizdən asılıdır: bütün sahələrə görə, bəzi sahələrə görə (məsələn, User üçün yalnız email) müqayisə etmək olar — əsas olan ardıcıllıq və kontrakta riayət etməkdir.
Bu harada xüsusilə vacibdir?
- Heş-cədvəl əsasında olan kolleksiyalarda: HashSet, HashMap, LinkedHashSet və s.
- Kolleksiyalarda axtarış və silmə zamanı: düzgün equals olmadan lazımi obyekt “tapılmaya” bilər.
- Biznes məntiqində: məsələn, eyni email-ə malik iki User eyni istifadəçi sayılmalıdır.
hashCode — nə üçün lazımdır?
Heş-kolleksiyalar (məsələn, HashSet, HashMap) heş-cədvəllərdən istifadə edir. hashCode metodu obyektin düşəcəyi “səbətin ünvanı” olan tam ədəd hesablayır. Əgər iki obyekt equals üzrə bərabərdirsə, onların hashCode-u da üst-üstə düşməlidir. Bu qaydanı pozsanız — kolleksiyalar gözlənilməz davranacaq.
2. equals və hashCode kontraktı
equals kontraktı
equals metodunun davranışı üçün əsas tələblər:
- Refleksivlik: a.equals(a) həmişə true.
- Simmetriklik: əgər a.equals(b) — true-dursa, b.equals(a) — də true-dur.
- Tranzitivlik: əgər a.equals(b) və b.equals(c) isə, a.equals(c) — də true olmalıdır.
- Uyğunluq: obyektlər dəyişməz qaldıqca nəticə sabitdir.
- null ilə müqayisə: istənilən obyekt null-a bərabər deyil.
hashCode kontraktı
- Əgər iki obyekt equals üzrə bərabərdirsə, onların hashCode-ları da bərabərdir.
- Əgər obyektlər bərabər deyilsə, onların heş-kodları üst-üstə düşə bilər (kolleziyalar mümkündür, amma arzuolunmazdır).
- Obyekt məntiqi olaraq dəyişmədiyi müddətdə onun hashCode-u sabit qalmalıdır.
Başqa sözlə, eyni hashCode — bərabərlik üçün zəruri, lakin kifayət olmayan şərtdir: eyni heş, equals üzrə bərabərliyi zəmanət etmir.
3. equals və hashCode-un reallaşdırılması: nümunə
Person sinfini nəzərdən keçirək; burada bərabərlik name və age sahələrinə görə müəyyən olunur.
public class Person {
private String name;
private int age;
// Konstruktor, getter-lər, setter-lər...
@Override
public boolean equals(Object o) {
if (this == o) return true; // İstinadların müqayisəsi
if (o == null || getClass() != o.getClass()) return false; // Sinifin yoxlanması
Person person = (Person) o; // Tipə çevirmə
// Sahələri müqayisə edirik
return age == person.age &&
(name != null ? name.equals(person.name) : person.name == null);
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age; // 31 — geniş yayılmış sadə ədəd seçimidir
return result;
}
}
- Əvvəlcə sürətli yoxlamalar: istinad və sinif.
- Sonra əhəmiyyətli sahələrin müqayisəsi.
- hashCode-da toqquşmaları azaltmaq üçün 31 sadə ədədindən istifadə olunur.
Objects.equals və Objects.hash istifadəsi
Java 7-dən başlayaraq, Objects sinfi kodu sadələşdirir və onu null-a qarşı daha təhlükəsiz edir:
import java.util.Objects;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age &&
Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
4. equals, hashCode və compareTo: necə əlaqəlidirlər
equals və compareTo necə əlaqəlidir?
Comparable interfeysi compareTo metodunu təyin edir; bu metod “kiçik/bərabər/böyük” üçün mənfi/sıfır/müsbət ədəd qaytarır. Arzu olunur ki, a.compareTo(b) == 0 olduqda a.equals(b) nəticəsi doğsun. Əksi mütləq deyil.
Uyğunluğu pozsanız (məsələn, compareTo yalnız yaşa görə müqayisə edir, equals isə ada və yaşa görə), TreeSet/TreeMap kimi sıralanan kolleksiyalar gözlənilməz davrana bilər: obyektlər nizam baxımından “bərabər”, məzmun baxımından isə bərabər sayılmaya bilər.
equals və hashCode kolleksiyalarda
- HashSet və HashMap-da əlavəetmə/axtarış/silmə əməliyyatları düzgün equals və hashCode reallaşdırmasına əsaslanır.
- Yenidən yazılmadan bu kolleksiyalar obyektləri “fərqli” sayır, hətta onların məlumatları eyni olsa belə.
5. Nümunələr: kolleksiyalarda bu necə işləyir
HashSet: unikal obyektlərin saxlanması
Set<Person> people = new HashSet<>();
people.add(new Person("Ivan", 20));
people.add(new Person("Ivan", 20)); // Dublikat
System.out.println(people.size()); // 1, əgər equals/hashCode düzgün reallaşdırılıbsa
Düzgün equals/hashCode olmadan dəstəyə hər iki obyekt düşəcək.
HashMap: açara görə axtarış
Map<Person, String> map = new HashMap<>();
Person p1 = new Person("Anna", 25);
Person p2 = new Person("Anna", 25);
map.put(p1, "İstifadəçi 1");
System.out.println(map.get(p2)); // "İstifadəçi 1", əgər equals/hashCode düzgün reallaşdırılıbsa
Kontrakt olmadan kolleksiya null qaytaracaq — onun üçün bunlar “fərqli” açarlardır.
6. Ən yaxşı təcrübələr: reallaşdırma üzrə məsləhətlər
- Obyektin “identikliyini” müəyyən edən bütün sahələri equals/hashCode-a daxil edin.
- hashCode hesablamasında (kolleksiyaya əlavə olunduqdan sonra dəyişən) dəyişən sahələrdən istifadə etməyin.
- Metodların generasiyasını IDE-yə həvalə edin — yazı səhvlərinin riski daha azdır.
- equals-da əvvəlcə this == o-nu, sonra sinfi, daha sonra sahələri yoxlayın.
- Sahələrin-obyektlərin müqayisəsi üçün Objects.equals-dən istifadə edin.
- Heş-kod üçün Objects.hash və ya 31 vurğulu sınaqdan keçmiş şablondan istifadə edin.
7. Faydalı incəliklər
Niyə yalnız hashCode istifadə etmək olmaz?
Kolleziyalar qaçılmazdır: müxtəlif obyektlərin hashCode-u eyni ola bilər. Heş — səbət üçün yalnız sürətli bələdçidir; bərabərlik barədə yekun qərarı equals verir.
equals və hashCode-u ümumiyyətlə yenidən yazmamaq olar?
Ancaq o halda ki, obyektlərin heç vaxt məzmununa görə müqayisə olunmayacağına və açar/unikal element kimi kolleksiyalarda istifadə olunmayacağına əminsiniz. Praktikada bu nadir haldır.
==, equals və compareTo arasındakı fərq
| Operator/metod | Nəyi müqayisə edir? | Nə üçün lazımdır? |
|---|---|---|
|
İstinadlar (yaddaş ünvanları) | “eyni obyekt?” yoxlanışı |
|
Obyektlərin məzmunu | Biznes məntiqinə görə bərabərlik |
|
Nizam (kiçik/bərabər/böyük) | Sıralama, nizamlama |
8. equals və hashCode-u reallaşdırarkən tipik səhvlər
Səhv №1: equals-u yenidən yazdınız, amma hashCode-u unutdunuz. Obyektlər bərabər sayılır, lakin heş-cədvəldə fərqli səbətlərə düşür — axtarış və silmə pozulur.
Səhv №2: hashCode-da dəyişən sahələrdən istifadə edirsiniz. Sahə kolleksiyaya əlavə edildikdən sonra dəyişərsə, obyekt “itirilə” bilər: heş dəyişir, amma səbət — yox.
Səhv №3: equals-un simmetrikliyi/tranzitivliyi pozulub. a.equals(b) true verir, b.equals(a) — false və ya tranzitivlik pozulur — kolleksiyalar gözlənilməz davranmağa başlayır.
Səhv №4: equals-da sinfi yoxlamırsınız. Müxtəlif siniflərin obyektlərini müqayisə etmək yanlış nəticələrə və ya istisnalara səbəb olur.
Səhv №5: sətirləri və obyektləri == ilə müqayisə edirsiniz. == operatoru istinadları müqayisə edir; məzmun üçün equals-dan istifadə edin.
Səhv №6: compareTo ilə equals arasında uyğunluq yoxdur. Əgər a.compareTo(b) == 0, amma !a.equals(b) isə, TreeSet/TreeMap kolleksiyaları elementləri nizam üçün bərabər, bərabərlik üçün isə fərqli saya bilər — bu “xəyal elementlər” və dublikatların mənbəyidir.
GO TO FULL VERSION