Jak sortować listę obiektów według jakiejś właściwości

145

Mam prostą klasę

public class ActiveAlarm {
    public long timeStarted;
    public long timeEnded;
    private String name = "";
    private String description = "";
    private String event;
    private boolean live = false;
}

i List<ActiveAlarm>przeciw. Jak sortować w kolejności rosnącej według timeStarted, a następnie według timeEnded? Czy ktoś może pomóc? Wiem w C ++ z generycznym algorytmem i operatorem przeciążenia <, ale jestem nowy w Javie.

Jennifer
źródło
Odpowiedź @Yishai w tym poście demonstruje eleganckie użycie wyliczenia do niestandardowego sortowania i sortowania grupowego (wiele argumentów) z wykorzystaniem łańcuchów porównawczych.
gunalmel

Odpowiedzi:

140

Utwórz ActiveAlarmimplementację Comparable<ActiveAlarm>lub zaimplementuj Comparator<ActiveAlarm>w oddzielnej klasie. Wtedy zadzwoń:

Collections.sort(list);

lub

Collections.sort(list, comparator);

Ogólnie rzecz biorąc, jest to dobry pomysł, aby wdrożyć Comparable<T>, jeśli istnieje jeden „naturalny” porządek ... inaczej (jeśli zdarzy się chce uporządkować w określonej kolejności, ale może równie łatwo chcą inną) to lepiej realizować Comparator<T>. Szczerze mówiąc, ta konkretna sytuacja może pójść w obie strony ... ale prawdopodobnie trzymałbym się bardziej elastycznej Comparator<T>opcji.

EDYCJA: Przykładowa implementacja:

public class AlarmByTimesComparer implements Comparator<ActiveAlarm> {
  @Override
  public int compare(ActiveAlarm x, ActiveAlarm y) {
    // TODO: Handle null x or y values
    int startComparison = compare(x.timeStarted, y.timeStarted);
    return startComparison != 0 ? startComparison
                                : compare(x.timeEnded, y.timeEnded);
  }

  // I don't know why this isn't in Long...
  private static int compare(long a, long b) {
    return a < b ? -1
         : a > b ? 1
         : 0;
  }
}
Jon Skeet
źródło
1
Ta funkcja compare () nie jest prawdopodobnie w Long, ponieważ implementacja jest jeszcze prostsza: return a - b;
papercrane
5
@papercrane: Nie, to nie działa z powodu przepełnienia. Rozważ a = Long.MIN_VALUE, b = 1.
Jon Skeet,
3
od API 19 (KitKat) Long ma teraz.compare
Martin Marconcini
123

Za pomocą Comparator

Na przykład:

class Score {

    private String name;
    private List<Integer> scores;
    // +accessor methods
}

    Collections.sort(scores, new Comparator<Score>() {

        public int compare(Score o1, Score o2) {
            // compare two instance of `Score` and return `int` as result.
            return o2.getScores().get(0).compareTo(o1.getScores().get(0));
        }
    });

Od wersji Java 8 możesz po prostu użyć wyrażenia lambda do reprezentowania instancji komparatora.

Collections.sort(scores, (s1, s2) -> { /* compute and return int */ });
Jigar Joshi
źródło
1
Co robi compareTo()? Skąd to pochodzi? Gdzie mam to zdefiniować?
Asqiir
1
@Asqiir getScores()to metoda pobierająca, dla scoresktórej jest plik List<Integer>. Kiedy getScores().get(0)dostaniesz Integerprzedmiot. Integerma już compareTo(anotherInteger)zaimplementowaną metodę, nie musisz jej definiować.
walen
co to jest get (0)?
Ali Khaki,
1
Dzięki działa jak urok (używam go bez części .get (0)). Jak mogę odwrócić kolejność?
53

Odpowiedź JAVA 8 i wyżej (przy użyciu wyrażeń lambda)

W Javie 8 wprowadzono wyrażenia Lambda, aby było to jeszcze łatwiejsze! Zamiast tworzyć obiekt Comparator () z całym jego szkieletem, możesz go uprościć w następujący sposób: (Używając swojego obiektu jako przykładu)

Collections.sort(list, (ActiveAlarm a1, ActiveAlarm a2) -> a1.timeStarted-a2.timeStarted);

lub jeszcze krócej:

Collections.sort(list, Comparator.comparingInt(ActiveAlarm ::getterMethod));

To jedno stwierdzenie jest równoważne z następującym:

Collections.sort(list, new Comparator<ActiveAlarm>() {
    @Override
    public int compare(ActiveAlarm a1, ActiveAlarm a2) {
        return a1.timeStarted - a2.timeStarted;
    }
});

Pomyśl o wyrażeniach Lambda jako wymagających jedynie umieszczenia odpowiednich części kodu: podpisu metody i tego, co zostanie zwrócone.

Inną częścią twojego pytania było to, jak porównać z wieloma polami. Aby to zrobić z wyrażeniami Lambda, możesz użyć .thenComparing()funkcji, aby skutecznie połączyć dwa porównania w jedno:

Collections.sort(list, (ActiveAlarm a1, ActiveAlarm a2) -> a1.timeStarted-a2.timeStarted             
       .thenComparing ((ActiveAlarm a1, ActiveAlarm a2) -> a1.timeEnded-a2.timeEnded)
);

Powyższy kod posortuje listę najpierw według timeStarted, a następnie według timeEnded(dla tych rekordów, które mają to samo timeStarted).

Ostatnia uwaga: łatwo jest porównać prymitywy „długie” lub „int”, wystarczy odjąć jeden od drugiego. Jeśli porównujesz obiekty („Long” lub „String”), sugeruję skorzystanie z ich wbudowanego porównania. Przykład:

Collections.sort(list, (ActiveAlarm a1, ActiveAlarm a2) -> a1.name.compareTo(a2.name) );

EDYCJA: Dziękuję Lukasowi Ederowi za wskazanie mi .thenComparing()funkcji.

John Fowler
źródło
4
Być może docenisz nowy interfejs API Java 8 Comparator.comparing().thenComparing()...
Lukas Eder
1
Uważaj na powracającą różnicę. W przypadku przepełnienia możesz uzyskać zły wynik.
krzychu
2
To jest IMHO, łatwiejsza metoda sortowania, ponownie zakładając, że nie masz oczywistej naturalnej kolejności. Nie musisz Collectionsjuż dzwonić , możesz zadzwonić bezpośrednio na listę. Na przykład:myList.sort(Comparator.comparing(Address::getZipCode).thenComparing(Compartor.comparing(Address::getStreetName));
CeePlusPlus
20

Listę możemy posortować na dwa sposoby:

1. Korzystanie z komparatora : Gdy wymagane jest użycie logiki sortowania w wielu miejscach Jeśli chcesz użyć logiki sortowania w jednym miejscu, możesz napisać anonimową klasę wewnętrzną w następujący sposób lub wyodrębnić komparator i użyć go w wielu miejscach

  Collections.sort(arrayList, new Comparator<ActiveAlarm>() {
        public int compare(ActiveAlarm o1, ActiveAlarm o2) {
            //Sorts by 'TimeStarted' property
            return o1.getTimeStarted()<o2.getTimeStarted()?-1:o1.getTimeStarted()>o2.getTimeStarted()?1:doSecodaryOrderSort(o1,o2);
        }

        //If 'TimeStarted' property is equal sorts by 'TimeEnded' property
        public int doSecodaryOrderSort(ActiveAlarm o1,ActiveAlarm o2) {
            return o1.getTimeEnded()<o2.getTimeEnded()?-1:o1.getTimeEnded()>o2.getTimeEnded()?1:0;
        }
    });

Moglibyśmy sprawdzić właściwości null, gdybyśmy mogli użyć „Long” zamiast „long”.

2. Korzystanie z porównywalnych (porządek naturalny) : Jeśli algorytm sortowania zawsze trzyma się jednej właściwości: napisz klasę, która implementuje „Porównywalny” i zastąp metodę „porównajTo”, jak zdefiniowano poniżej

class ActiveAlarm implements Comparable<ActiveAlarm>{

public long timeStarted;
public long timeEnded;
private String name = "";
private String description = "";
private String event;
private boolean live = false;

public ActiveAlarm(long timeStarted,long timeEnded) {
    this.timeStarted=timeStarted;
    this.timeEnded=timeEnded;
}

public long getTimeStarted() {
    return timeStarted;
}

public long getTimeEnded() {
    return timeEnded;
}

public int compareTo(ActiveAlarm o) {
    return timeStarted<o.getTimeStarted()?-1:timeStarted>o.getTimeStarted()?1:doSecodaryOrderSort(o);
}

public int doSecodaryOrderSort(ActiveAlarm o) {
    return timeEnded<o.getTimeEnded()?-1:timeEnded>o.getTimeEnded()?1:0;
}

}

wywołaj metodę sortowania, aby sortować na podstawie naturalnego porządku

Collections.sort(list);
Jagadeesh
źródło
7

W java8 + można to zapisać w jednej linii w następujący sposób:

collectionObjec.sort(comparator_lamda) lub comparator.comparing(CollectionType::getterOfProperty)

kod:

ListOfActiveAlarmObj.sort((a,b->a.getTimeStarted().compareTo(b.getTimeStarted())))
 

lub

ListOfActiveAlarmObj.sort(Comparator.comparing(ActiveAlarm::getTimeStarted))
Karthikeyan
źródło
5
public class ActiveAlarm implements Comparable<ActiveAlarm> {
    public long timeStarted;
    public long timeEnded;
    private String name = "";
    private String description = "";
    private String event;
    private boolean live = false;

    public int compareTo(ActiveAlarm a) {
        if ( this.timeStarted > a.timeStarted )
            return 1;
        else if ( this.timeStarted < a.timeStarted )
            return -1;
        else {
             if ( this.timeEnded > a.timeEnded )
                 return 1;
             else
                 return -1;
        }
 }

To powinno dać ci ogólne pojęcie. Gdy to zrobisz, możesz zadzwonić Collections.sort()na listę.

Kal
źródło
4

Od Java8 można to zrobić jeszcze czystsze przy użyciu kombinacji ComparatoriLambda expressions

Na przykład:

class Student{

    private String name;
    private List<Score> scores;

    // +accessor methods
}

class Score {

    private int grade;
    // +accessor methods
}

    Collections.sort(student.getScores(), Comparator.comparing(Score::getGrade);
Mathias G.
źródło
2

Porównanie guawy Łańcuch :

Collections.sort(list, new Comparator<ActiveAlarm>(){
            @Override
            public int compare(ActiveAlarm a1, ActiveAlarm a2) {
                 return ComparisonChain.start()
                       .compare(a1.timestarted, a2.timestarted)
                       //...
                       .compare(a1.timeEnded, a1.timeEnded).result();
            }});
卢 声 远 Shengyuan Lu
źródło
1

W java musisz użyć Collections.sortmetody statycznej . Oto przykład listy obiektów CompanyRole, posortowanych najpierw według początku, a następnie według końca. Możesz łatwo dostosować się do własnego obiektu.

private static void order(List<TextComponent> roles) {

    Collections.sort(roles, new Comparator() {
        @Override
        public int compare(Object o1, Object o2) {
            int x1 = ((CompanyRole) o1).getBegin();
            int x2 = ((CompanyRole) o2).getBegin();

            if (x1 != x2) {
                return x1 - x2;
            } else {
                int y1 = ((CompanyRole) o1).getEnd();
                int y2 = ((CompanyRole) o2).getEnd();
                return y2 - y1;
            }
        }
    });
}
Richard H.
źródło
0

Możesz wywołać Kolekcje.sort () i przekazać komparator, który musisz napisać, aby porównać różne właściwości obiektu.

Liv
źródło
0

Jak wspomniano, możesz sortować według:

  • Implementacja obiektu Comparable
  • Lub podaj ComparatordoCollections.sort

Jeśli zrobisz oba, Comparablezostaną zignorowane i Comparatorzostaną użyte. Pomaga to w tym, że obiekty wartości mają własną logikę, Comparablektóra jest najbardziej rozsądnym sortowaniem dla obiektu wartości, podczas gdy każdy indywidualny przypadek użycia ma swoją własną implementację.

Alireza Fattahi
źródło