Jelenlegi hely

21.1.5 Objektumok rendezése

Rendezzünk egy tetszőleges tartalmú l listát:

Collections.sort(l);

Ha a lista String-eket tartalmaz, akkor azt (Unicode-beli) abc sorrendbe rendezhetjük. Ha pedig Dátum (Date) tagokat tartalmaz, akkor időrendi sorrendbe. Hogyan történik ez? A String és a Date is megvalósítják a Comparable interfészt. Ez az interfész tartalmazza a legáltalánosabb rendezési eljárásokat, amely megengedi az osztálynak, hogy automatikusan rendezve legyen valamely szempont szerint. A következő táblázat összefoglalja a legfontosabb osztályokat, amelyek az interfészt megvalósítják.

Osztály Alapértelmezett sorrend
Byte előjeles szám
Character előjelnélküli szám
Long előjeles szám
Integer előjeles szám
Short előjeles szám
Double előjeles szám
Float előjeles szám
BigInteger előjeles szám
BigDecimal előjeles szám
Boolean false < true
File rendszerfüggő abc szerinti a teljes név alapján
String abc szerinti
Date időrendi
CollationKey abc szerinti a helyi jellemzők alapján

Ha a listánk olyan objektumokat tartalmaz, amelyek nem valósítják meg a Comparable interfészt, akkor a Collections.sort(list) hívás ClassCastException kivételt fog dobni.

Saját összehasonlítható osztály létrehozása

A Comparable interfész egyetlen metódust tartalmaz:

public interface Comparable<T> {
    public int compareTo(T o);
}

A comprateTo metódus összehasonlítja az objektumot az átvett objektummal, és visszatérési értéke negatív egész, nulla, vagy pozitív egész, ha az átvevő objektum kisebb, egyenlő vagy nagyobb, mint az átvett objektum. Ha a metódus nem tudja összehasonlítani a két objektumot, akkor ClassCastException-t dob.

A következő osztály emberek nevét tartalmazza összehasonlító eszközökkel kiegészítve:

import java.util.*;
public final class Name implements Comparable<Name> {
    private final String firstName, lastName;
    public Name(String firstName, String lastName) {
        if (firstName == null || lastName == null)
            throw new NullPointerException();
        this.firstName = firstName;
        this.lastName = lastName;
    }
    public String firstName() { return firstName; }
    public String lastName()  { return lastName;  }
    public boolean equals(Object o) {
        if (!(o instanceof Name))
            return false;
        Name n = (Name) o;
        return n.firstName.equals(firstName) &&
            n.lastName.equals(lastName);
    }
    public int hashCode() {
        return 31*firstName.hashCode() + lastName.hashCode();
    }
    public String toString() {
        return firstName + " " + lastName;
    }
    public int compareTo(Name n) {
        int lastCmp = lastName.compareTo(n.lastName);
        return (lastCmp != 0 ? lastCmp :
                firstName.compareTo(n.firstName));
    }
}

Ebben a rövid példában az osztály némileg korlátozott: nem támogatja a középső nevet, kötelező megadni a vezeték- és keresztnevet, és nem veszi figyelembe a nyelvi sajátosságokat. Azonban így is illusztrál néhány fontos pontot:

  • A konstruktor ellenőrzi, hogy az paraméterei null értékűek-e. Ez minden létrejövő Name objektum számára biztosítja, hogy jól formázott legyen, így semelyik más metódus nem dob NullPointerException-t.
  • A hashCode metódus újradefiniált. (Azonos objektumoknak azonos hash kódjuknak kell lennie)
  • Az equals metódus false értékkel tér vissza, ha a paraméterként kapott másik objektum null, vagy helytelen típus. Ilyen esetben a compareTo metódus futásidejű kivételt dob.
  • A toString metódus újradefiniálásával a Name olvasható formában jeleníthető meg. Ez mindig jó ötlet, különösen olyan objektumok esetén, amiket gyűjteménybe helyezünk. A különböző típusú gyűjtemények toString metódusai jól olvasható formában jelenítik meg a gyűjtemények tartalmát.
  • A comparateTo metódus összehasonlítja az objektumok legfontosabb részét (lastName). (Mint ahogy itt is, máskor is gyakran tudjuk használni a részek típusa szerinti alapértelmezett rendezést.) A lastName adattag String típusú, így az alapértelmezett rendezés pontosan megfelelő lesz. Ha az összehasonlítási eredmények nullától különbözőek, amely egyenlőséget jelent, kész vagyunk: csak vissza kell adni az eredményt. Ha a legfontosabb részek egyenlők, összehasonlítjuk a következőket (itt firstName). Ha ez alapján sem dönthető el a sorrend, továbblépünk.

A következő példa félépíti a nevek listáját:

import java.util.*;
public static void main(String[] args) {
        Name nameArray[] = {
            new Name("John", "Lennon"),
            new Name("Karl", "Marx"),
            new Name("Groucho", "Marx"),
            new Name("Oscar", "Grouch")
        };
        List<Name> names = Arrays.asList(nameArray);
        Collections.sort(names);
        System.out.println(names);
    }
}

A program futása után a következőt írja ki:

[Oscar Grouch, John Lennon, Groucho Marx, Karl Marx]

A Comparator-ok

Hogyan tudjuk az objektumokat az alapértelmezett rendezéstől eltérő más sorrendbe rendezni? Ilyen esetben jön jól a Comparator interfész, amely egyetlen egyszerű metódust tartalmaz:

public interface Comparator<T> {
    int compare(T o1, T o2);
}

A compare metódus összehasonlítja a két paramétert, visszatérési értéke negatív egész, nulla, vagy pozitív egész, ha az első paraméter kisebb, egyelő vagy nagyobb, mint a második. Ha valamelyik paraméter helytelen típusú a Comparator számára, a compare metódus ClassCastException-t dob.

Tegyük fel, hogy van egy Employee nevű osztályunk:

public class Employee implements Comparable<Employee> {
    public Name name()     { ... }
    public int number()    { ... }
    public Date hireDate() { ... }
        ...
}

Ha az Employee objektumok alapértelmezett rendezése a Name tag szerinti, akkor nem egyszerű a legmagasabb rangú dolgozókat kikeresni a listából. A következő program elkészíti a listát:

import java.util.*;
class EmpSort {
    static final Comparator<Employee> SENIORITY_ORDER =
                                 new Comparator<Employee>() {
        public int compare(Employee e1, Employee e2) {
            return e2.hireDate().compareTo(e1.hireDate());
        }
    };
    // Employee Database
    static final Collection<Employee> employees = ... ;
    public static void main(String[] args) {
        List<Employee>e = new ArrayList<Employee>(employees);
        Collections.sort(e, SENIORITY_ORDER);
        System.out.println(e);
    }
}