Sortowanie ArrayList obiektów przy użyciu niestandardowej kolejności sortowania


119

Chcę zaimplementować funkcję sortowania dla mojej aplikacji książki adresowej.

Chcę posortować ArrayList<Contact> contactArray. Contactto klasa, która zawiera cztery pola: imię i nazwisko, numer domowy, numer telefonu komórkowego i adres. Chcę posortować dalej name.

Jak mogę napisać niestandardową funkcję sortowania, aby to zrobić?

Odpowiedzi:


270

Oto samouczek dotyczący zamawiania obiektów:

Chociaż podam kilka przykładów, to i tak polecam je przeczytać.


Istnieją różne sposoby sortowania plików ArrayList. Jeśli chcesz zdefiniować naturalne (domyślne) uporządkowanie , musisz pozwolić Contactimplementować Comparable. Zakładając, że chcesz sortować domyślnie name, zrób (dla uproszczenia pominięto kontrole null):

public class Contact implements Comparable<Contact> {

    private String name;
    private String phone;
    private Address address;

    public int compareTo(Contact other) {
        return name.compareTo(other.name);
    }

    // Add/generate getters/setters and other boilerplate.
}

więc możesz po prostu to zrobić

List<Contact> contacts = new ArrayList<Contact>();
// Fill it.

Collections.sort(contacts);

Jeśli chcesz zdefiniować porządkowanie sterowane zewnętrznie (które zastępuje porządek naturalny), musisz utworzyć Comparator:

List<Contact> contacts = new ArrayList<Contact>();
// Fill it.

// Now sort by address instead of name (default).
Collections.sort(contacts, new Comparator<Contact>() {
    public int compare(Contact one, Contact other) {
        return one.getAddress().compareTo(other.getAddress());
    }
}); 

Możesz nawet zdefiniować Comparators w Contactsobie, aby móc ich używać ponownie, zamiast tworzyć je za każdym razem:

public class Contact {

    private String name;
    private String phone;
    private Address address;

    // ...

    public static Comparator<Contact> COMPARE_BY_PHONE = new Comparator<Contact>() {
        public int compare(Contact one, Contact other) {
            return one.phone.compareTo(other.phone);
        }
    };

    public static Comparator<Contact> COMPARE_BY_ADDRESS = new Comparator<Contact>() {
        public int compare(Contact one, Contact other) {
            return one.address.compareTo(other.address);
        }
    };

}

które można wykorzystać w następujący sposób:

List<Contact> contacts = new ArrayList<Contact>();
// Fill it.

// Sort by address.
Collections.sort(contacts, Contact.COMPARE_BY_ADDRESS);

// Sort later by phone.
Collections.sort(contacts, Contact.COMPARE_BY_PHONE);

A żeby ubić górę, możesz rozważyć użycie ogólnego komparatora javabean :

public class BeanComparator implements Comparator<Object> {

    private String getter;

    public BeanComparator(String field) {
        this.getter = "get" + field.substring(0, 1).toUpperCase() + field.substring(1);
    }

    public int compare(Object o1, Object o2) {
        try {
            if (o1 != null && o2 != null) {
                o1 = o1.getClass().getMethod(getter, new Class[0]).invoke(o1, new Object[0]);
                o2 = o2.getClass().getMethod(getter, new Class[0]).invoke(o2, new Object[0]);
            }
        } catch (Exception e) {
            // If this exception occurs, then it is usually a fault of the developer.
            throw new RuntimeException("Cannot compare " + o1 + " with " + o2 + " on " + getter, e);
        }

        return (o1 == null) ? -1 : ((o2 == null) ? 1 : ((Comparable<Object>) o1).compareTo(o2));
    }

}

którego możesz użyć w następujący sposób:

// Sort on "phone" field of the Contact bean.
Collections.sort(contacts, new BeanComparator("phone"));

(jak widać w kodzie, prawdopodobnie pola puste są już zakryte, aby uniknąć NPE podczas sortowania)


2
Dodałbym możliwość wcześniejszego zdefiniowania kilku komparatorów, a następnie użycie ich po nazwie ...
Stobor

2
W rzeczywistości właśnie to zrobiłem. Łatwiej niż próbować się wytłumaczyć.
Stobor

@BalusC: Nie ma problemu. Nie mogę przypisać sobie tego pomysłu, dostałem go od String.CASE_INSENSITIVE_ORDERi przyjaciół, ale podoba mi się. Sprawia, że ​​wynikowy kod jest łatwiejszy do odczytania.
Stobor

1
Te definicje Komparatora prawdopodobnie powinny być, statica może i finalteż ... Lub coś w tym rodzaju ...
Stobor

Heheh ... BeanComparator jest jak Awesome On A Stick! :-) (Nie pamiętam dokładnego porównania logicznego wartości null, ale czy chce on (o1 == null && o2 == null) ? 0 :na początku tej linii powrotu?)
Stobor

28

Oprócz tego, co już zostało opublikowane, powinieneś wiedzieć, że od Java 8 możemy skrócić nasz kod i napisać go tak:

Collection.sort(yourList, Comparator.comparing(YourClass::getFieldToSortOn));

lub ponieważ Lista ma teraz sortmetodę

yourList.sort(Comparator.comparing(YourClass::getFieldToSortOn));

Wyjaśnienie:

Od wersji Java 8 interfejsy funkcjonalne (interfejsy z tylko jedną metodą abstrakcyjną - mogą mieć więcej metod domyślnych lub statycznych) można łatwo zaimplementować za pomocą:

Ponieważ Comparator<T>ma tylko jedną abstrakcyjną metodę, int compare(T o1, T o2)jest to interfejs funkcjonalny.

Więc zamiast (przykład z odpowiedzi @BalusC )

Collections.sort(contacts, new Comparator<Contact>() {
    public int compare(Contact one, Contact other) {
        return one.getAddress().compareTo(other.getAddress());
    }
}); 

możemy zredukować ten kod do:

Collections.sort(contacts, (Contact one, Contact other) -> {
     return one.getAddress().compareTo(other.getAddress());
});

Możemy uprościć tę (lub dowolną) lambdę, pomijając

  • typy argumentów (Java wywnioskuje je na podstawie sygnatury metody)
  • lub {return...}

Więc zamiast

(Contact one, Contact other) -> {
     return one.getAddress().compareTo(other.getAddress();
}

możemy pisać

(one, other) -> one.getAddress().compareTo(other.getAddress())

Teraz Comparatormamy również metody statyczne, takie jak comparing(FunctionToComparableValue)lub, comparing(FunctionToValue, ValueComparator)których moglibyśmy użyć do łatwego tworzenia komparatorów, które powinny porównywać określone wartości z obiektów.

Innymi słowy, możemy przepisać powyższy kod jako

Collections.sort(contacts, Comparator.comparing(Contact::getAddress)); 
//assuming that Address implements Comparable (provides default order).

8

Ta strona zawiera wszystko, co musisz wiedzieć o sortowaniu kolekcji, takich jak ArrayList.

Zasadniczo musisz

  • Contactspraw, aby Twoja klasa implementowała Comparableinterfejs przez
    • tworzenie public int compareTo(Contact anotherContact)w niej metody .
  • Gdy to zrobisz, możesz po prostu zadzwonić Collections.sort(myContactList);,
    • gdzie myContactListjest ArrayList<Contact>(lub jakikolwiek inny zbiór Contact).

Istnieje również inny sposób, polegający na utworzeniu klasy komparatora, o czym możesz przeczytać również na połączonej stronie.

Przykład:

public class Contact implements Comparable<Contact> {

    ....

    //return -1 for less than, 0 for equals, and 1 for more than
    public compareTo(Contact anotherContact) {
        int result = 0;
        result = getName().compareTo(anotherContact.getName());
        if (result != 0)
        {
            return result;
        }
        result = getNunmber().compareTo(anotherContact.getNumber());
        if (result != 0)
        {
            return result;
        }
        ...
    }
}

5

BalusC i bguiz udzieliły już bardzo wyczerpujących odpowiedzi na temat korzystania z wbudowanych komparatorów Javy.

Chcę tylko dodać, że kolekcje Google mają klasę Ordering, która jest bardziej „potężna” niż standardowe komparatory. Może warto to sprawdzić. Możesz robić fajne rzeczy, takie jak składanie kolejności, odwracanie ich, porządkowanie w zależności od wyniku funkcji dla twoich obiektów ...

Oto post na blogu, w którym wymieniono niektóre z jego zalet.


Zauważ, że kolekcje google są teraz częścią Guava (wspólne biblioteki java Google), więc możesz chcieć polegać na Guava (lub module kolekcji Guava), jeśli chcesz używać klasy Ordering.
Etienne Neveu

4

Musisz sprawić, aby klasy Contact implementowały porównywalną , a następnie zaimplementuj compareTo(Contact)metodę. W ten sposób Kolekcje.sort będą mogły je dla Ciebie sortować. Na stronie, z którą się łączyłem, funkcja compareTo „zwraca ujemną liczbę całkowitą, zero lub dodatnią liczbę całkowitą, ponieważ ten obiekt jest mniejszy, równy lub większy od określonego obiektu”.

Na przykład, jeśli chcesz posortować według nazwy (od A do Z), Twoja klasa wyglądałaby tak:

public class Contact implements Comparable<Contact> {

    private String name;

    // all the other attributes and methods

    public compareTo(Contact other) {
        return this.name.compareTo(other.name);
    }
}

Dobrze ze mną współpracował, dzięki! Użyłem również compareToIgnoreCase, aby zignorować wielkość liter.
Rani Kheir

3

Używając lambdaj , możesz posortować kolekcję swoich kontaktów (na przykład według ich nazw) w następujący sposób

sort(contacts, on(Contact.class).getName());

lub przez ich adres:

sort(contacts, on(Contacts.class).getAddress());

i tak dalej. Mówiąc bardziej ogólnie, oferuje DSL do uzyskiwania dostępu do kolekcji i manipulowania nimi na wiele sposobów, takich jak filtrowanie lub grupowanie kontaktów na podstawie pewnych warunków, agregowanie niektórych wartości ich właściwości itp.


0

Kolekcje.sort to dobra implementacja. Jeśli nie masz implementacji porównywalnej dla Contact, musisz przejść implementację komparatora

Nutowy:

Algorytm sortowania to zmodyfikowany scalanie (w którym scalanie jest pomijane, jeśli najwyższy element na niskiej podliście jest mniejszy niż najniższy element na najwyższej podliście). Ten algorytm zapewnia gwarantowaną wydajność n log (n). Określona lista musi być modyfikowalna, ale nie musi być skalowalna. Ta implementacja zrzuca określoną listę do tablicy, sortuje tablicę i wykonuje iterację po liście, resetując każdy element z odpowiedniej pozycji w tablicy. Pozwala to uniknąć wydajności n2 log (n), która wynikałaby z próby posortowania połączonej listy w miejscu.

Sortowanie przez scalanie jest prawdopodobnie lepsze niż większość algorytmów wyszukiwania, które możesz wykonać.


0

Zrobiłem to w następujący sposób. number i name to dwa arraylist. Muszę posortować nazwę. Jeśli zdarzy się jakakolwiek zmiana w kolejności nazewnictwa, wówczas arraylista numerów również zmieni swoją kolejność.

public void sortval(){

        String tempname="",tempnum="";

         if (name.size()>1) // check if the number of orders is larger than 1
            {
                for (int x=0; x<name.size(); x++) // bubble sort outer loop
                {
                    for (int i=0; i < name.size()-x-1; i++) {
                        if (name.get(i).compareTo(name.get(i+1)) > 0)
                        {

                            tempname = name.get(i);

                            tempnum=number.get(i);


                           name.set(i,name.get(i+1) );
                           name.set(i+1, tempname);

                            number.set(i,number.get(i+1) );
                            number.set(i+1, tempnum);


                        }
                    }
                }
            }



}

Pisanie tego zajmie ci więcej czasu, uzyskasz mniej optymalną wydajność sortowania, napiszesz więcej błędów (i miejmy nadzieję, że więcej testów), a kod będzie trudniejszy do przekazania innym osobom. Więc to nie w porządku. To może działać, ale to nie czyni tego poprawnym.
Eric

0

użyj tej metody:

private ArrayList<myClass> sortList(ArrayList<myClass> list) {
    if (list != null && list.size() > 1) {
        Collections.sort(list, new Comparator<myClass>() {
            public int compare(myClass o1, myClass o2) {
                if (o1.getsortnumber() == o2.getsortnumber()) return 0;
                return o1.getsortnumber() < o2.getsortnumber() ? 1 : -1;
            }
        });
    }
    return list;
}

`

i użyj: mySortedlist = sortList(myList); Nie ma potrzeby wdrażania komparatora w swojej klasie. Jeśli chcesz zmienić kolejność odwrotną 1i-1


0

Ok, wiem, że odpowiedziano na to dawno temu ... ale oto kilka nowych informacji:

Powiedzmy, że dana klasa Contact ma już zdefiniowaną naturalną kolejność poprzez implementację Comparable, ale chcesz zastąpić tę kolejność, powiedzmy, po nazwie. Oto nowoczesny sposób, aby to zrobić:

List<Contact> contacts = ...;

contacts.sort(Comparator.comparing(Contact::getName).reversed().thenComparing(Comparator.naturalOrder());

W ten sposób najpierw posortuje według nazwy (w odwrotnej kolejności), a następnie w przypadku kolizji nazw powróci do „naturalnego” porządku zaimplementowanego przez samą klasę Contact.


-1

Powinieneś użyć funkcji Arrays.sort. Klasy zawierające powinny implementować Comparable.


Dlatego powiedziałem Arrays.
mnich

Problem polega na tym, że OP używa ArrayList, a nie array.
Pshemo
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.