CodeGym /Kurslar /JAVA 25 SELF /equals və hashCode kontraktları

equals və hashCode kontraktları

JAVA 25 SELF
Səviyyə , Dərs
Mövcuddur

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: equalshashCode 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)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 nameage 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

  • HashSetHashMap-da əlavəetmə/axtarış/silmə əməliyyatları düzgün equalshashCode 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ışı
equals
Obyektlərin məzmunu Biznes məntiqinə görə bərabərlik
compareTo
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.

Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION