MySQL Java JavaScript PHP Python HTML-CSS C-sharp

Interfețele Comparable și Comparator - Sortare

În tema anterioară am discutat despre funcționarea colecției TreeSet, tipizată cu obiecte de tip String. La adăugarea de noi elemente, TreeSet sortează automat obiectele, plasându-le în ordinea corectă. Cu șiruri de caractere, acest lucru este simplu. Dar ce se întâmplă dacă folosim propriile noastre clase, cum ar fi următoarea clasă Person?

class Person {
     
   private String name;
   Person(String name){
       this.name = name;
   }
   String getName() {
       return name;
   }
}

Nu putem tipiza un obiect TreeSet cu această clasă, deoarece, atunci când adăugăm obiecte, TreeSet nu va ști cum să le compare, și următorul cod nu va funcționa:

TreeSet<Person> people = new TreeSet<Person>();
people.add(new Person("Tom"));

La rularea acestui cod, vom întâlni o eroare care spune că obiectul Person nu poate fi convertit în tipul java.lang.Comparable.

Pentru ca obiectele Person să poată fi comparate și sortate, ele trebuie să implementeze interfața Comparable<E>. Aceasta va fi tipizată cu clasa curentă. Aplicăm interfața clasei Person astfel:

class Person implements Comparable<Person> {
   
   private String name;
   Person(String name) {
       this.name = name;
   }
   String getName() {
       return name;
   }
   
   public int compareTo(Person p) {
       return name.compareTo(p.getName());
   }
}

Interfața Comparable conține o singură metodă: int compareTo(E item), care compară obiectul curent cu obiectul transmis ca parametru. Dacă metoda returnează un număr negativ, obiectul curent va fi plasat înaintea celui transmis. Dacă returnează un număr pozitiv, va fi plasat după acesta. Dacă returnează zero, cele două obiecte sunt considerate egale.

În acest caz, ne bazăm pe mecanismul de comparare integrat al clasei String, dar putem defini propria logică, de exemplu, comparând în funcție de lungimea numelui:

public int compareTo(Person p) {
   return name.length() - p.getName().length();
}

Acum putem tipiza TreeSet cu tipul Person și adăuga obiecte în arbore:

TreeSet<Person> people = new TreeSet<Person>();
people.add(new Person("Tom"));

Interfața Comparator

Problema apare atunci când clasa pe care dorim să o folosim nu implementează interfața Comparable, sau când dorim să redefinim logica de comparare. În acest caz, putem folosi interfața Comparator<E>, care este mai flexibilă.

Interfața Comparator conține metoda cheie compare():

public interface Comparator<E> {
   int compare(T a, T b);
   // alte metode
}

Metoda compare() returnează un număr: negativ dacă obiectul a precede b, pozitiv dacă îl urmează, iar zero dacă sunt egale. Pentru a aplica această interfață, creăm o clasă care implementează Comparator:

class PersonComparator implements Comparator<Person> {
   public int compare(Person a, Person b) {
       return a.getName().compareTo(b.getName());
   }
}

Apoi, folosim clasa comparator pentru a crea un TreeSet:

PersonComparator pcomp = new PersonComparator();
TreeSet<Person> people = new TreeSet<Person>(pcomp);
people.add(new Person("Tom"));
people.add(new Person("Nick"));
people.add(new Person("Alice"));
people.add(new Person("Bill"));

for (Person p : people) {
   System.out.println(p.getName());
}

În acest caz, constructorul TreeSet primește comparatorul ca parametru. Astfel, indiferent dacă Person implementează sau nu interfața Comparable, logica de comparare definită în PersonComparator va fi utilizată.

Sortare după mai multe criterii

Începând cu JDK 8, au fost introduse îmbunătățiri în mecanismul de funcționare al comparatorilor. De exemplu, acum putem aplica mai mulți comparatori în lanț. Să modificăm clasa Person pentru a include și vârsta:

class Person {
     
   private String name;
   private int age;
   public Person(String n, int a) {
       name = n;
       age = a;
   }
   String getName() {
       return name;
   }
   int getAge() {
       return age;
   }
}

Vom defini doi comparatori: unul pentru nume și unul pentru vârstă:

class PersonNameComparator implements Comparator<Person> {
   public int compare(Person a, Person b) {
       return a.getName().compareTo(b.getName());
   }
}

class PersonAgeComparator implements Comparator<Person> {
   public int compare(Person a, Person b) {
       if (a.getAge() > b.getAge()) {
           return 1;
       } else if (a.getAge() < b.getAge()) {
           return -1;
       } else {
           return 0;
       }
   }
}

Interfața comparator are o metodă specială thenComparing, care permite utilizarea mai multor comparatori în lanț:

Comparator<Person> pcomp = new PersonNameComparator().thenComparing(new PersonAgeComparator());
TreeSet<Person> people = new TreeSet(pcomp);
people.add(new Person("Tom", 23));
people.add(new Person("Nick", 34));
people.add(new Person("Tom", 10));
people.add(new Person("Bill", 14));

for (Person p : people) {
   System.out.println(p.getName() + " " + p.getAge());
}

Rezultatul afișat în consolă va fi:

Bill 14 
Nick 34 
Tom 10 
Tom 23

În acest exemplu, mai întâi se aplică sortarea după nume, iar apoi după vârstă, dacă numele sunt identice.

← Lecția anterioară Lecția următoare →