1. Giriş
Həyatda obyektləri müqayisə etməyin tək bir üsulu çox vaxt kifayət etmir. Təsəvvür edin ki, sizdə istifadəçilərin siyahısı var: bəzən onları ada görə, bəzən — yaşa görə, bəzən isə — familiyanın uzunluğuna görə sıralamaq istəyirsiniz. Yaxud siz müəllifi olmadığınız bir sinifə sahibsiniz və ona compareTo əlavə etmək mümkün deyil. Məhz belə hallar üçün Java‑da Comparator interfeysi mövcuddur.
Comparable kifayət etmədikdə
- Sinfi dəyişmək olmur (məsələn, üçüncü tərəf kitabxanasındandır).
- Bir neçə sıralama üsuluna ehtiyac var (müxtəlif sahələr üzrə).
- Müqayisə məntiqini sinifdən ayırmaq istəyirsiniz (məsələn, proqramın müxtəlif hissələrində fərqli qaydada sıralamaq).
Analogiya
Əgər Comparable obyektin daxili «təbii qaydası»dırsa, Comparator xarici bir hakimdir; o, obyektlərinizi istənilən meyarla qiymətləndirə bilər: bu gün ada görə, sabah — yaşa görə, birisi gün — adın uzunluğuna görə.
2. Comparator interfeysi: sintaksis və müqavilə
Interfeysin elan edilməsi
public interface Comparator<T> {
int compare(T o1, T o2);
}
compare metodu aşağıdakıları qaytarmalıdır:
- Birinci obyekt ikinciyə nisbətən «kiçikdirsə», mənfi ədəd.
- 0, bərabərdirsə.
- Birinci «daha böyükdürsə», müsbət ədəd.
Müqavilə Comparable ilə eynidir, sadəcə indi iki obyekt bir‑biri ilə müqayisə olunur, «cari» və «başqa» obyekt isə compareTo vasitəsilə deyil.
Nümunə: familiyaya görə sıralamaq üçün komparator
Tutaq ki, bizdə Person sinfi var:
public class Person {
private String firstName;
private String lastName;
private int age;
// Konstruktor və getter-lər
public Person(String firstName, String lastName, int age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}
public String getFirstName() { return firstName; }
public String getLastName() { return lastName; }
public int getAge() { return age; }
}
Familiyaya görə sıralayan komparator yaradaq:
import java.util.Comparator;
public class LastNameComparator implements Comparator<Person> {
@Override
public int compare(Person a, Person b) {
return a.getLastName().compareTo(b.getLastName());
}
}
Qeyd: sətirlərdə (String) compareTo metodu onları əlifba sırası ilə müqayisə edir.
3. Comparator-dan istifadə: kolleksiyaların sıralanması
Komparatorla sıralama
import java.util.*;
public class Main {
public static void main(String[] args) {
List<Person> people = new ArrayList<>();
people.add(new Person("Anna", "Kostetskaya", 25));
people.add(new Person("Boris", "Novak", 20));
people.add(new Person("Viktoriya", "Bell", 22));
// Familiyaya görə sıralama
Collections.sort(people, new LastNameComparator());
for (Person p : people) {
System.out.println(p.getLastName() + " " + p.getFirstName());
}
}
}
Nəticə:
Novak Boris
Kostetskaya Anna
Bell Viktoriya
Komparator vasitəsilə yaşa görə sıralama
Sinif artıq ada görə Comparable reallaşdırsa belə, ayrıca komparator — yaşa görə — yarada bilərsiniz:
public class AgeComparator implements Comparator<Person> {
@Override
public int compare(Person a, Person b) {
return Integer.compare(a.getAge(), b.getAge());
}
}
Və oxşar şəkildə istifadə etmək:
Collections.sort(people, new AgeComparator());
Nəticə:
Boris Novak (20)
Viktoriya Bell (22)
Anna Kostetskaya (25)
Nümunə: komparatorun «yerindəcə» seçilməsi
Collections.sort(people, new LastNameComparator()); // Familiyaya görə
Collections.sort(people, new AgeComparator()); // Yaşa görə
4. Anonim siniflər və lambda ifadələri
Komparatorları ayrıca sinif elan etmədən «yerindəcə» yaratmaq olar.
Anonim sinif
Collections.sort(people, new Comparator<Person>() {
@Override
public int compare(Person a, Person b) {
return a.getFirstName().compareTo(b.getFirstName());
}
});
Lambda ifadəsi
Collections.sort(people, (a, b) -> a.getFirstName().compareTo(b.getFirstName()));
Yaxud daha qısa — siyahının List.sort metodu ilə:
people.sort((a, b) -> a.getFirstName().compareTo(b.getFirstName()));
- Anonim siniflər — köhnə üsuldur, həcmli və uzundur.
- Lambda — müasir və yığcamdır.
5. Nümunələr: müxtəlif meyarlara görə sıralama
Familiyanın uzunluğuna görə sıralama
Comparator<Person> byLastNameLength = (a, b) ->
Integer.compare(a.getLastName().length(), b.getLastName().length());
people.sort(byLastNameLength);
Əvvəl yaşa, sonra ada görə sıralama (çoxsəviyyəli)
Comparator<Person> byAgeThenName = (a, b) -> {
int cmp = Integer.compare(a.getAge(), b.getAge());
if (cmp != 0) return cmp;
return a.getFirstName().compareTo(b.getFirstName());
};
people.sort(byAgeThenName);
Axtarış üçün komparatorun istifadəsi (nümunə)
Komparator təkcə sıralama üçün deyil, həm də sıralanmış kolleksiyalarda axtarış üçün uyğundur:
// people siyahısı yaşa görə sıralanmış olmalıdır!
Person key = new Person("?", "?", 22);
int idx = Collections.binarySearch(people, key, new AgeComparator());
if (idx >= 0) {
System.out.println("Yaşı 22 olan şəxs tapıldı: " + people.get(idx));
}
6. Ən yaxşı təcrübələr və Comparator ilə işin xüsusiyyətləri
Müqaviləni pozmayın
- Əgər compare(a, b) 0 qaytarırsa, onda compare(b, a) də 0 qaytarmalıdır.
- Əgər compare(a, b) > 0-dırsa, compare(b, a) < 0 olmalıdır.
- Mümkün null dəyərləri nəzərə alın (aşağıya baxın).
equals və hashCode haqqında unutmayın
Komparatorlar obyektləri «öz qaydasında» müqayisə etsə də, TreeSet kimi strukturlar və ya TreeMap-də açar axtarışı üçün komparator üzrə müqayisə məntiqinin equals ilə uyğun olması vacibdir. Əks halda gözlənilməz nəticələr ala bilərsiniz: iki müxtəlif obyekt komparatora görə bərabər sayılır, lakin equals üzrə bərabər deyil.
null nəzərə alınmaqla sıralama
Sahələr null ola bilərsə, «hazır» köməkçilərdən istifadə edin:
Comparator<Person> byLastNameNullSafe = Comparator.comparing(
Person::getLastName,
Comparator.nullsLast(String::compareTo)
);
people.sort(byLastNameNullSafe);
7. Faydalı incəliklər
Cədvəl: Comparable və Comparator müqayisəsi
| Comparable | Comparator | |
|---|---|---|
| Harada reallaşdırılır? | Birbaşa sinfin özündə | Ayrı sinifdə/lambdada |
| Metod | |
|
| Neçə variant? | Yalnız bir «təbii» | İstədiyiniz qədər, hər cür ehtiyac üçün |
| Tətbiq | |
|
| Başqa (xarici) siniflər üçün mümkündür? | Xeyr | Bəli |
Nümunə: azalan qaydada sıralama
Qaydaları əl ilə tərsinə çevirmək olar:
Comparator<Person> byAgeDesc = (a, b) -> Integer.compare(b.getAge(), a.getAge());
people.sort(byAgeDesc);
Yaxud reversed() köməyi ilə:
Comparator<Person> byAge = Comparator.comparingInt(Person::getAge);
people.sort(byAge.reversed());
8. Comparator ilə işləyərkən tipik səhvlər
Səhv № 1: müqayisə müqaviləsinin pozulması. Əgər compare(a, b) və compare(b, a) nəticələrinin işarə etibarilə bir-birinə əks olmalı olduğunu unudursunuzsa və ya təsadüfi dəyərlər qaytarırsınızsa (məsələn, sadəcə fərqi — a.getAge() - b.getAge(), bu, daşma yarada bilər), nəticə proqnozlaşdırılmayan olacaq. Integer.compare istifadə edin, çıxmadan — bu, daha təhlükəsizdir.
Səhv № 2: null dəyərlərin nəzərə alınmaması. Müqayisə etdiyiniz sahələr null ola bilirsə, mütləq bu halı emal edin (məsələn, Comparator.nullsFirst/Comparator.nullsLast vasitəsilə), əks halda ən gözlənilməz anda NullPointerException ala bilərsiniz.
Səhv № 3: qeyri-sabit sıralama meyarları. Əgər komparator eyni obyektlər üçün fərqli dəyərlər qaytarırsa (məsələn, təsadüfi ədəd və ya çox dəyişən sahə istifadə edir), sıralama xaotik davrana bilər.
Səhv № 4: equals ilə uyğunsuzluq. Əgər compare(a, b) == 0, amma a.equals(b) false-dursa, TreeSet və TreeMap kimi kolleksiyalar gözlədiyiniz kimi işləməyə bilər. Arzuolunandır ki, komparator üzrə bərabərlik və equals üzrə bərabərlik üst-üstə düşsün.
Səhv № 5: xarici siniflər üçün komparatorsuz sıralama. Əgər Comparable olmayan «xarici» sinifin obyektlərini və Comparator ötürmədən sıralamağa çalışsanız, kompilyasiya xətası alacaqsınız. Açıq komparator ötürün.
GO TO FULL VERSION