1. Obyektlərin müqayisəsi problemi
Xatırlatma: istinadların müqayisəsi və obyektlərin müqayisəsi
Artıq bilirsiniz ki, Java-da == operatoru obyektlərlə işləyərkən onların istinadlarını müqayisə edir — yəni yaddaşda eyni ünvanda olub-olmadıqlarını. Eyni sahələri olan iki obyekt, lakin new vasitəsilə yaradılıbsa, == üçün fərqli olacaq.
Person p1 = new Person("Sasha", 20);
Person p2 = new Person("Sasha", 20);
System.out.println(p1 == p2); // false — bunlar yaddaşda fərqli obyektlərdir!
Əgər məzmununa görə bərabər olub-olmadığını bilmək istəyiriksə, equals(), hashCode() istifadə edirik... bunu artıq bilirsiniz. Bəs “kim daha yaşlıdır”, “kim daha gəncdir”, “əlifbaca kim daha əvvəldir” kimi suallara necə baxaq? Məsələn, istifadəçilər siyahısını yaşa və ya ada görə çeşidləmək üçün.
Obyektlərin çeşidlənməsi və axtarışı zərurəti
Tutaq ki, bizdə istifadəçilərin siyahısı var və onları yaşa görə çeşidləmək istəyirik:
List<Person> people = new ArrayList<>();
people.add(new Person("Vasya", 25));
people.add(new Person("Petya", 20));
people.add(new Person("Katya", 30));
// Necə çeşidləyək?
Collections.sort(people); // Aha! Java Person-u necə müqayisə etməli olduğunu bilmir!
Kompilyator dərhal şikayət edəcək: Person sinfi Comparable interfeysini reallaşdırmır. Java beynimizi oxuya bilmir və Person üçün “böyük” və ya “kiçik” nə deməkdir — bilmir. Bunu ona öyrətmək üçün müqayisə qaydalarını açıq şəkildə təsvir etməliyik.
2. Comparable interfeysi
İnterfeysin elan edilməsi
Comparable interfeysi Java-ya belə deməyin standart yoludur: “Mənim sinfim müqayisə oluna bilər və bunu belə etmək lazımdır”.
public interface Comparable<T> {
int compareTo(T o);
}
Köhnə tanış a.compareTo(b) aşağıdakıları qaytaracaq:
- mənfi ədəd — deməli, a b-dən “kiçikdir”.
- Əgər 0-dırsa — obyektlər bərabər sayılır.
- müsbət ədəd — a b-dən “böyükdür”.
Nümunə: Person sinfi üçün compareTo reallaşdırması
Gəlin yaşa görə müqayisə oluna bilən Person sinfi yaradaq:
public class Person implements Comparable<Person> {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// Getter-lər (daha aşağıdakı nümunələr üçün)
public String getName() { return name; }
public int getAge() { return age; }
// compareTo metodunun reallaşdırılması
@Override
public int compareTo(Person other) {
// Yaşa görə çeşidləmə (artma sırası ilə)
return Integer.compare(this.age, other.age);
// Alternativ: return this.age - other.age;
}
}
Vacib məqam: əgər azalan qaydada çeşidləmək istəyirsinizsə, sadəcə arqumentlərin yerlərini dəyişin: Integer.compare(other.age, this.age).
Analoqiya. compareTo — yarışda hakim kimidir: o, kimin qabaqda, kimin arxada, kimin isə eyni səviyyədə olduğunu dəqiq müəyyən etməlidir. Əgər bütün hakimlər (compareTo metodları) fərqli qaydada qərar versələr — xaos başlayacaq!
3. Comparable-dən istifadə
Comparable ilə kolleksiyaların çeşidlənməsi
Artıq sinfimiz Comparable-i reallaşdırdığından, çeşidləmə “qutudan çıxan” kimi işləyir:
List<Person> people = new ArrayList<>();
people.add(new Person("Vasya", 25));
people.add(new Person("Petya", 20));
people.add(new Person("Katya", 30));
Collections.sort(people); // compareTo istifadə olunur!
for (Person p : people) {
System.out.println(p.getName() + " (" + p.getAge() + ")");
}
// Petya (20)
// Vasya (25)
// Katya (30)
Eyni şəkildə siyahının sort metodu da işləyir:
people.sort(null); // null ötürülsə, compareTo istifadə olunur
Ada görə çeşidləmə
Əgər ada görə çeşidləmək istəyiriksə — reallaşdırmanı dəyişirik:
@Override
public int compareTo(Person other) {
return this.name.compareTo(other.name);
}
Bir neçə sahə üzrə çeşidləmə
Bəzən əvvəl bir sahəyə görə, bərabər olduqda isə digərinə görə müqayisə etmək lazımdır:
@Override
public int compareTo(Person other) {
int cmp = Integer.compare(this.age, other.age);
if (cmp != 0) return cmp;
return this.name.compareTo(other.name);
}
4. Comparable reallaşdırılmasında ən yaxşı təcrübələr
Comparable müqaviləsinə əməl edin
- Əgər a.compareTo(b) == 0-dırsa, o zaman b.compareTo(a) mütləq 0 olmalıdır.
- Əgər a.compareTo(b) < 0-dırsa, b.compareTo(a) > 0 olmalıdır (və əksinə).
- Əgər a.compareTo(b) == 0-dırsa, yaxşı olardı ki, a.equals(b) də true olsun (amma bu sərt tələb deyil).
Bu niyə vacibdir?
Kolleksiyalar (məsələn, TreeSet, TreeMap) və çeşidləmə metodları müqavilə pozularsa, gözlənilməz davranış göstərə bilər. Məsələn, dublikatlar, əslində olmamalı olduqları halda, kolleksiyada peyda ola bilər.
equals və hashCode barədə unutmayın
Əgər compareTo reallaşdırırsınızsa, düşünün: equals və hashCode düzgünmü reallaşdırılıb? Xüsusən də sinfiniz HashSet və ya Map tipli kolleksiyalarda istifadə olunacaqsa.
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Person)) return false;
Person other = (Person) o;
return age == other.age && Objects.equals(name, other.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
Yoxlamasız compareTo daxilində null ola bilən sahələrdən istifadə etməyin!
Əgər sahə null ola bilərsə, təhlükəsiz müqayisələrdən istifadə edin:
@Override
public int compareTo(Person other) {
return Objects.compare(this.name, other.name, Comparator.nullsFirst(String::compareTo));
}
Obyekt artıq çeşidlənmiş kolleksiyada olduqda, compareTo-da iştirak edən sahələri dəyişməyin
Bu, obyektin kolleksiya daxilində “itirilməsinə” səbəb ola bilər — məsələn, TreeSet və ya TreeMap-də.
5. Tədris tətbiqini inkişaf etdiririk: istifadəçilərin çeşidlənməsi
Addım 1: Sinfi təsvir edirik
public class Person implements Comparable<Person> {
private String name;
private int age;
// ... konstruktor, getter-lər, compareTo, equals, hashCode ...
}
Addım 2: İstifadəçiləri əlavə edirik
List<Person> people = new ArrayList<>();
people.add(new Person("Masha", 23));
people.add(new Person("Grisha", 19));
people.add(new Person("Anya", 25));
Addım 3: Çeşidləyirik və çap edirik
Collections.sort(people);
for (Person p : people) {
System.out.println(p.getName() + " (" + p.getAge() + ")");
}
Nəticə:
Grisha (19)
Masha (23)
Anya (25)
6. Comparable-in iş sxemi
┌────────────────────────────┐
│ Sizin sinfiniz (Person) │
├────────────────────────────┤
│ implements Comparable │
│ ↓ │
│ public int compareTo(T o) │
│ ↓ │
│ (this < o) → -1 │
│ (this == o) → 0 │
│ (this > o) → 1 │
└────────────────────────────┘
│
▼
Collections.sort(list)
│
▼
Çeşidləmə işləyir!
7. Faydalı nüanslar
Collections.sort necə işləyir
- Əgər siyahı Comparable-i reallaşdıran obyektlərdən ibarətdirsə, çeşidləmə onların compareTo metodundan istifadə edəcək.
- Əgər reallaşdırmırsa — kompilyasiya xətası olacaq.
- Standart tiplər üçün (Integer, String və s.) Comparable artıq reallaşdırılıb.
Bir neçə müqayisə üsulunu etmək olar?
- Bir sinifdə — yalnız bir “təbii sıra” Comparable vasitəsilə olur.
- Alternativ sıralar üçün Comparator-dan istifadə edin (növbəti mühazirə).
Nümunə: sətirlər üçün compareTo
String a = "apple";
String b = "banana";
System.out.println(a.compareTo(b)); // mənfi ədəd, çünki "apple" < "banana"
Cədvəl: compareTo nə qaytarır
| Müqayisə | Qaytarılan dəyər |
|---|---|
|
|
|
|
|
|
8. Comparable reallaşdırılmasında tipik səhvlər
Səhv №1: compareTo müqaviləsinin pozulması.
Əgər a.compareTo(b) 0 qaytarır, amma b.compareTo(a) 0 qaytarmırsa, kolleksiyalar qəribə davranacaq. Məsələn, TreeSet obyektləri fərqli hesab edib hər ikisini də əlavə edə bilər.
Səhv №2: İlkinləşdirilməmiş (null) sahələrin istifadəsi.
Müqayisə etdiyiniz sahə null ola bilirsə və siz yoxlama etmirsinizsə — NullPointerException alacaqsınız.
Səhv №3: compareTo və equals arasında uyğunsuzluq.
Əgər compareTo obyektlərin bərabər olduğunu deyirsə (0), amma equals — fərqli olduğunu (false) deyirsə, bu, kolleksiyalarla işləyərkən xətalara gətirib çıxaracaq.
Səhv №4: Obyekt çeşidlənmiş kolleksiyaya əlavə edildikdən sonra compareTo-da iştirak edən sahələrin dəyişdirilməsi.
Bu, sanki artıq əlifba sırasına görə növbədə dayanarkən pasportda soyadınızı dəyişmək kimidir. Kolleksiya obyektinizi “itirdə” bilər.
Səhv №5: Yalnız -1, 0 və ya 1 qaytarmaq.
compareTo metodu istənilən mənfi və ya müsbət ədəd qaytara bilər, mütləq -1 və ya 1 olmalı deyil. Amma sadəlik üçün tez-tez -1/0/1 istifadə olunur.
GO TO FULL VERSION