Como classificar a lista de objetos por alguma propriedade

145

Eu tenho aula simples

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

e List<ActiveAlarm>contra. Como classificar em ordem crescente por timeStartede depois por timeEnded? Alguém pode ajudar? Eu sei em C ++ com algoritmo genérico e operador de sobrecarga <, mas eu sou novo em Java.

Jennifer
fonte
Responder por @Yishai neste post demonstra o uso elegante de enum para classificação personalizada e classificação agrupada (vários argumentos) utilizando o encadeamento do comparador.
gunalmel

Respostas:

140

Crie ActiveAlarmimplementar Comparable<ActiveAlarm>ou implemente Comparator<ActiveAlarm>em uma classe separada. Então ligue:

Collections.sort(list);

ou

Collections.sort(list, comparator);

Em geral, é uma boa idéia para implementar Comparable<T>se há uma única ordem de classificação "natural" ... caso contrário (se você acontecer querer classificar em uma ordem específica, mas pode igualmente facilmente quer um diferente) é melhor implementar Comparator<T>. Essa situação em particular poderia ser de qualquer maneira, para ser honesto ... mas eu provavelmente ficaria com a Comparator<T>opção mais flexível .

Edição: Exemplo de implementação:

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
fonte
1
Essa função compare () não está em Long provavelmente porque a implementação é ainda mais trivial: return a - b;
Papercrane
5
@ papercrane: Não, isso falha por razões de estouro. Considere a = Long.MIN_VALUE, b = 1.
Jon Skeet
3
a partir da API 19 (KitKat) Long agora tem um.compare
Martin Marconcini
123

Usando Comparator

Por exemplo:

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));
        }
    });

Com o Java 8 em diante, você pode simplesmente usar a expressão lambda para representar a instância do Comparator.

Collections.sort(scores, (s1, s2) -> { /* compute and return int */ });
Jigar Joshi
fonte
1
O que compareTo()faz? De onde isso vem? Onde eu tenho que defini-lo?
Asqiir
1
@Asqiir getScores()é o getter para o scoresqual é um List<Integer>. Quando você getScores().get(0)recebe um Integerobjeto. Integerjá tem o compareTo(anotherInteger)método implementado, você não precisa defini-lo.
walen
o que é get (0)?
Ali Cáqui
1
Obrigado funciona como um encanto (eu uso sem a parte .get (0)). Como eu poderia reverter a ordem?
53

Resposta JAVA 8 e acima (usando expressões lambda)

No Java 8, expressões Lambda foram introduzidas para tornar isso ainda mais fácil! Em vez de criar um objeto Comparator () com todos os seus andaimes, você pode simplificá-lo da seguinte maneira: (Usando seu objeto como exemplo)

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

ou ainda mais curto:

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

Essa declaração é equivalente ao seguinte:

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

Pense nas expressões Lambda como exigindo apenas que você insira as partes relevantes do código: a assinatura do método e o que é retornado.

Outra parte da sua pergunta foi como comparar com vários campos. Para fazer isso com expressões Lambda, você pode usar a .thenComparing()função para combinar efetivamente duas comparações em uma:

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

O código acima classificará a lista primeiro por timeStartede depois por timeEnded(para os registros que têm o mesmo timeStarted).

Uma última observação: é fácil comparar primitivas 'longas' ou 'int', você pode apenas subtrair uma da outra. Se você estiver comparando objetos ('Long' ou 'String'), sugiro que você use a comparação interna. Exemplo:

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

EDIT: Obrigado a Lukas Eder por me indicar para .thenComparing()funcionar.

John Fowler
fonte
4
Você pode apreciar o novo Java 8 API Comparator.comparing().thenComparing()...
Lukas Eder
1
Cuidado com a diferença de retorno. Em caso de estouro, você pode obter um resultado errado.
krzychu
2
Este é o método de classificação mais fácil para IMHO, assumindo novamente que você não tem uma ordem natural óbvia. Você também não precisa Collectionsmais ligar , pode ligar diretamente para a lista. Por exemplo:myList.sort(Comparator.comparing(Address::getZipCode).thenComparing(Compartor.comparing(Address::getStreetName));
CeePlusPlus
20

Podemos classificar a lista de duas maneiras:

1. Usando o comparador : quando necessário para usar a lógica de classificação em vários locais Se você quiser usar a lógica de classificação em um único local, poderá escrever uma classe interna anônima da seguinte forma ou extrair o comparador e usá-lo em vários locais

  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;
        }
    });

Podemos ter uma verificação nula para as propriedades, se pudéssemos usar 'Long' em vez de 'long'.

2. Usando Comparable (ordenação natural) : Se o algoritmo de classificação sempre se ater a uma propriedade: escreva uma classe que implemente o método 'Comparable' e substitua 'compareTo', conforme definido abaixo

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;
}

}

método de classificação de chamada para classificar com base em pedidos naturais

Collections.sort(list);
Jagadeesh
fonte
7

No java8 +, isso pode ser escrito em uma única linha, da seguinte maneira:

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

código:

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

ou

ListOfActiveAlarmObj.sort(Comparator.comparing(ActiveAlarm::getTimeStarted))
Karthikeyan
fonte
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;
        }
 }

Isso deve lhe dar uma idéia aproximada. Feito isso, você pode ligar Collections.sort()na lista.

Kal
fonte
4

Desde o Java8, isso pode ser feito ainda mais limpo usando uma combinação de ComparatoreLambda expressions

Por exemplo:

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.
fonte
2

Guava ComparisonChain :

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
fonte
1

Em java, você precisa usar o Collections.sortmétodo estático . Aqui está um exemplo para uma lista de objetos CompanyRole, classificados primeiro por begin e depois por end. Você pode se adaptar facilmente ao seu próprio objeto.

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
fonte
0

Você pode chamar Collections.sort () e passar um comparador que você precisa escrever para comparar diferentes propriedades do objeto.

Liv
fonte
0

Como mencionado, você pode classificar por:

  • Fazendo seu objeto implementar Comparable
  • Ou passe um ComparatorparaCollections.sort

Se você fizer os dois, o Comparableserá ignorado e Comparatorserá usado. Isso ajuda que os objetos de valor tenham sua própria lógica, Comparableque é a classificação mais razoável para seu objeto de valor, enquanto cada caso de uso individual tem sua própria implementação.

Alireza Fattahi
fonte