CodeGym /Kurslar /JAVA 25 SELF /equals, hashCode, toString: avtomatik generasiya

equals, hashCode, toString: avtomatik generasiya

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

1. equals, hashCode, toString-un avtomatik generasiyası

Bu metodlar nə üçün lazımdır?

Java-da obyektlərlə işləyərkən tez-tez eyni tapşırıqlarla qarşılaşırsınız. Bəzən iki obyektin bərabər olub-olmadığını yoxlamaq lazım olur. Məsələn, onun artıq Set və ya Map kimi kolleksiyada olub-olmadığını anlamaq. Başqa hallarda obyekt HashMap-də açar kimi istifadə olunur və burada xüsusi müqayisə qaydaları olmadan mümkün deyil. Və demək olar ki, həmişə obyekti loqda və ya ekranda çap etmək istəyirik ki, çıxış sadəcə MyClass@7b23ec81 kimi abrakadabra yox, mənalı bir şey olsun.

Bu hallar üçün Java-da hər sinifin üç xüsusi metodu var:

  • equals(Object o) bərabərliyin yoxlanmasına cavabdehdir.
  • hashCode() obyektə kolleksiyalar üçün lazım olan sayısal «barmaq izi» verir.
  • toString() obyektin rahat sətir təqdimatını qaytarır və sazlama ilə çapı xeyli asanlaşdırır.

Niyə bu, adi siniflərdə çətindir?

Adi siniflərdə bu metodları əl ilə yazmaq lazımdır. Və burada həm darıxdırıcı, həm də başağrısı başlayır. Sinfi yalnız dolduran bir yığın şablon kod alınır. Haradasa səhv etmək çox asandır: sahəni müqayisə etməyi unutmaq, hashCode-u səhv hesablamaq və sonra müəmmalı bağları tutmaq. Və sinfə yeni sahə əlavə etsəniz — yenidən bu metodların hamısına baxıb hər şeyi yenidən yazmaq lazım gələcək.

Adi sinif nümunəsi


public class Point {
    private final int x;
    private final int y;

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public int x() { return x; }
    public int y() { return y; }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Point point = (Point) o;
        return x == point.x && y == point.y;
    }

    @Override
    public int hashCode() {
        return 31 * x + y;
    }

    @Override
    public String toString() {
        return "Point[x=" + x + ", y=" + y + "]";
    }
}

Tanınmış görünür? Bəli, və bu cəmi iki sahə üçündür! Bəs iyirmi olsaydı?

Bunu record necə edir

Record-sinif bunların hamısını sizin yerinizə edir. Sadəcə belə elan edin:


public record Point(int x, int y) { }

Və Java özü generasiya edəcək:

  • Konstruktoru
  • Getter-ləri (x(), y())
  • equals, hashCode, toString

Avtomatik yaradılan metodlar

  • equals record-un bütün komponentlərini dəyərə görə müqayisə edir.
  • hashCode bütün komponentlər əsasında hesablanır.
  • toString Point[x=1, y=2] şəklində sətir qaytarır.

Gəlin bunu canlı görək!


public record Point(int x, int y) {}

public class Demo {
    public static void main(String[] args) {
        Point p1 = new Point(1, 2);
        Point p2 = new Point(1, 2);

        System.out.println(p1.equals(p2)); // true
        System.out.println(p1.hashCode() == p2.hashCode()); // true
        System.out.println(p1); // Point[x=1, y=2]
    }
}

Çıxış:

true
true
Point[x=1, y=2]

Hər şey gözlənildiyi kimi işləyir — artıq bir sətir kod belə yazmadan!

2. Niyə bu vacibdir: kolleksiyalar, sazlama və təhlükəsizlik

Kolleksiyalarda düzgün işləmə

Təsəvvür edin ki, obyektləri HashMap-də açar və ya HashSet-də element kimi istifadə edirsiniz. Əgər equals və hashCode səhv reallaşdırılıbsa — kolleksiyalar qəribə davranacaq: az əvvəl əlavə etdiyiniz elementi tapa bilməyəcək və ya əksinə, iki fərqli obyekti eyni sayacaq.

Record-siniflərlə əmin ola bilərsiniz: müqayisə və hash həmişə record-un bütün komponentlərini (elan olunduqları ardıcıllıqla) nəzərə alır.

Nümunə: record-u açar kimi istifadə etmək


import java.util.HashMap;
import java.util.Map;

public class Demo {
    public static void main(String[] args) {
        record Point(int x, int y) {}

        Map<Point, String> map = new HashMap<>();
        Point p1 = new Point(3, 4);
        map.put(p1, "Hello!");

        Point p2 = new Point(3, 4);
        System.out.println(map.get(p2)); // "Hello!" — işləyir!
    }
}

Diqqət yetirin: p1 və p2 — fərqli obyektlərdir (fərqli istinadlar), lakin sahə dəyərləri eynidir, ona görə də bərabər sayılırlar. Map və HashMap barədə daha ətraflı 26-cı səviyyədə öyrənəcəksiniz :P

Sazlama və loqlama rahatlığı

Adi siniflərdə standart olaraq olduğu kimi darıxdırıcı Point@1a2b3c4d əvəzinə, record-sinif gözəl və informativ şəkildə çap olunur:

Point[x=3, y=4]

Bu, sazlama və loqlama zamanı çox vaxt qazandırır.

3. record daxilində equals, hashCode, toString necə işləyir

equals metodu

Record-sinif equals-i elə reallaşdırır ki, iki obyekt aşağıdakı hallarda bərabər sayılır:

  • Eyni tipdədirlər (eyni record sinfi)
  • Bütün komponentləri bərabərdir (== primitivlər üçün, obyektlər üçün equals())

Müqayisə nümunəsi


Point p1 = new Point(1, 2);
Point p2 = new Point(1, 2);
Point p3 = new Point(1, 3);

System.out.println(p1.equals(p2)); // true
System.out.println(p1.equals(p3)); // false

hashCode metodu

Hash-kod record-un bütün komponentləri əsasında hesablanır, adətən standart Objects.hash(...) metodu ilə.


System.out.println(p1.hashCode()); // Məsələn, 994
System.out.println(p2.hashCode()); // Eləcə də 994
System.out.println(p3.hashCode()); // Başqa rəqəm

toString metodu

Strinq təqdimat həmişə bu formatdadır:

ClassName[field1=value1, field2=value2, ...]

System.out.println(p1); // Point[x=1, y=2]

4. equals, hashCode, toString-i yenidən təyin etmək: nə vaxt və necə?

Bəzən (nadir hallarda, amma olur) bu metodların standart davranışını dəyişmək lazımdır. Məsələn, toString-in başqa formatda sətir qaytarmasını və ya müqayisənin yalnız sahələrin bir hissəsinə görə aparılmasını istəyirsiniz.

Diqqət: əgər equals/hashCode-u yenidən təyin edirsinizsə, bunu çox düşünülmüş şəkildə edin! Onların «kontraktını» pozmaq tutulması çətin olan bağlara səbəb ola bilər.

Metodu necə yenidən təyin etməli

Sadəcə öz metodunuzu record-sinifin gövdəsində elan edin:


public record Point(int x, int y) {
    @Override
    public String toString() {
        return "(" + x + "; " + y + ")";
    }
}

Point p = new Point(3, 5);
System.out.println(p); // (3; 5)

equals/hashCode-u yenidən təyin etmək olarmı?

Bəli, amma nə etdiyinizdən əmin deyilsinizsə, qətiyyən məsləhət görülmür. Məsələn, müqayisənin yalnız x sahəsinə görə getməsini istəyirsinizsə (bu artıq qəribədir):


public record Point(int x, int y) {
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Point other)) return false;
        return x == other.x;
    }

    @Override
    public int hashCode() {
        return Integer.hashCode(x);
    }
}

Point p1 = new Point(1, 2);
Point p2 = new Point(1, 999);

System.out.println(p1.equals(p2)); // true (!)

Amma ehtiyatlı olun: əgər equals-i yenidən təyin edirsinizsə, həmişə hashCode-u da yenidən təyin edin — əks halda kolleksiyalar düzgün işləməyəcək.

Best practice

  • Niyə yenidən təyin etməli olduğunuzu dəqiq bilmirsinizsə — heç etməyin!
  • toString üçün — istəsəniz öz formatınızı rahatlıqla verə bilərsiniz.
  • equals/hashCode üçün — yalnız ciddi səbəb varsa və nəticələrini anlayırsınızsa.

5. Təcrübə: obyektlərin müqayisəsi və record-un kolleksiyalarda istifadəsi

Nümunə: iki record-obyektin müqayisəsi


public record User(String name, int age) {}

public class Demo {
    public static void main(String[] args) {
        User u1 = new User("Alice", 20);
        User u2 = new User("Alice", 20);
        User u3 = new User("Bob", 25);

        System.out.println(u1.equals(u2)); // true
        System.out.println(u1.equals(u3)); // false

        System.out.println(u1.hashCode() == u2.hashCode()); // true
        System.out.println(u1); // User[name=Alice, age=20]
    }
}

Nümunə: record-u HashMap-də açar kimi istifadə etmək

Gəlin təsəvvür edək ki, tətbiqimizdə istifadəçilərin ad və yaşına görə ziyarət sayını saxlayırıq (kim bilir, bəlkə klubda «Ivan 20 yaş» adlı iki nəfər var).


import java.util.HashMap;
import java.util.Map;

public class Demo {
    public static void main(String[] args) {
        record User(String name, int age) {}

        Map<User, Integer> visits = new HashMap<>();
        User ivan20 = new User("Ivan", 20);
        User ivan22 = new User("Ivan", 22);

        visits.put(ivan20, 5);
        visits.put(ivan22, 2);

        // Dəyərə görə axtarışın düzgün işlədiyini yoxlayaq
        System.out.println(visits.get(new User("Ivan", 20))); // 5
        System.out.println(visits.get(new User("Ivan", 22))); // 2
    }
}

Əgər equals və hashCode düzgün reallaşdırılmasaydı, axtarış işləməzdi. Map və HashMap barədə daha ətraflı 26-cı səviyyənin dərslərindən öyrənəcəksiniz :P

6. record-siniflərdə equals, hashCode, toString ilə işləyərkən tipik səhvlər

Səhv №1: Sahələrin yaradıldıqdan sonra dəyişdirilə biləcəyini gözləmək.
Record sahələri həmişə final-dır və müqayisə konstruktorda verilən dəyərlərə görə aparılır. Əgər hər hansı hiylə ilə daxili vəziyyəti «dəyişsəniz» (məsələn, sahə daxilində dəyişən obyekt vasitəsilə), müqayisə və hash səhv ola bilər.

Səhv №2: equals-u yenidən təyin etdiniz, amma hashCode-u unutdunuz.
Bu metodlardan birini yenidən təyin edirsinizsə — hər zaman digərini də edin! Əks halda kolleksiyalar (HashSet, HashMap) qeyri-proqnozlaşdırılan kimi davranacaq.

Səhv №3: toString-in başqa formatda olacağını gözləmək.
Sizə xüsusi sətir formatı lazımdırsa — sadəcə toString-i yenidən təyin edin. Susmaya görə format həmişə ClassName[field1=value1, field2=value2]-dir.

Səhv №4: mutable sahələri olan mürəkkəb siniflər üçün record-dan istifadə.
Record sahələri dəyişməz olmalıdır. Məsələn, sahə kimi ArrayList istifadə edirsinizsə və kimsə onun məzmununu dəyişirsə — müqayisə və hash-kod «sınıla» bilər. Record üçün yalnız dəyişməz tiplərdən istifadə etmək daha yaxşıdır.

Səhv №5: davranışı value-object olmayan siniflər üçün record-dan istifadə.
Record — «qısa sintaksisli kiçik sinif» deyil. O, dəyərlər toplusunu saxlamaq üçün nəzərdə tutulmuş məhz value-object-dir. Əgər sizdə mürəkkəb məntiq, dəyişən (mutable) vəziyyət və ya irsilik tələb olunursa — adi sinifdən istifadə edin.

1
Tapşırıq
JAVA 25 SELF, səviyyə, dərs
Bağlanıb
Kitabxana hesabatında kitab detalları 📚
Kitabxana hesabatında kitab detalları 📚
1
Tapşırıq
JAVA 25 SELF, səviyyə, dərs
Bağlanıb
Xəritədə mövqe identifikasiyası 📍
Xəritədə mövqe identifikasiyası 📍
1
Tapşırıq
JAVA 25 SELF, səviyyə, dərs
Bağlanıb
İstifadəçi profilinin gözəl təqdimatı 🧑‍💻
İstifadəçi profilinin gözəl təqdimatı 🧑‍💻
1
Tapşırıq
JAVA 25 SELF, səviyyə, dərs
Bağlanıb
Mağazada məhsulların adla identifikasiyası 🏷️
Mağazada məhsulların adla identifikasiyası 🏷️
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION