Classificar ArrayList de objetos personalizados por propriedade


1145

Eu li sobre a classificação de ArrayLists usando um Comparador, mas em todos os exemplos que as pessoas usavam, compareToque de acordo com algumas pesquisas são um método para Strings.

Eu queria classificar um ArrayList de objetos personalizados por uma de suas propriedades: um objeto Date ( getStartDay()). Normalmente eu os comparo, item1.getStartDate().before(item2.getStartDate())então eu queria saber se eu poderia escrever algo como:

public class CustomComparator {
    public boolean compare(Object object1, Object object2) {
        return object1.getStartDate().before(object2.getStartDate());
    }
}

public class RandomName {
    ...
    Collections.sort(Database.arrayList, new CustomComparator);
    ...
}


2
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:


1538

Desde Dateimplementa Comparable, ele tem um compareTométodo como Stringfaz.

Portanto, seu costume Comparatorpode ficar assim:

public class CustomComparator implements Comparator<MyObject> {
    @Override
    public int compare(MyObject o1, MyObject o2) {
        return o1.getStartDate().compareTo(o2.getStartDate());
    }
}

O compare()método deve retornar um int, para que você não possa retornar diretamente um booleancomo estava planejando.

Seu código de classificação seria exatamente como você escreveu:

Collections.sort(Database.arrayList, new CustomComparator());

Uma maneira um pouco mais curta de escrever tudo isso, se você não precisar reutilizar seu comparador, é escrevê-lo como uma classe anônima embutida:

Collections.sort(Database.arrayList, new Comparator<MyObject>() {
    @Override
    public int compare(MyObject o1, MyObject o2) {
        return o1.getStartDate().compareTo(o2.getStartDate());
    }
});

Desde a

Agora você pode escrever o último exemplo em um formato mais curto usando uma expressão lambda para Comparator:

Collections.sort(Database.arrayList, 
                        (o1, o2) -> o1.getStartDate().compareTo(o2.getStartDate()));

E Listpossui um sort(Comparator)método, para que você possa encurtar ainda mais:

Database.arrayList.sort((o1, o2) -> o1.getStartDate().compareTo(o2.getStartDate()));

Esse é um idioma tão comum que existe um método interno para gerar a Comparatorpara uma classe com uma Comparablechave:

Database.arrayList.sort(Comparator.comparing(MyObject::getStartDate));

Todos estes são formas equivalentes.


33
+1 por mencionar que ele deve retornar inte que é melhor usá-lo Date#compareTo()para isso. Por que isso não foi votado acima, a outra resposta está além de mim. Esse link também pode ser útil: Tutorial de pedido de objetos em Sun.com .
BalusC

5
Eu acho que a resposta melhor também deve incluir a maneira correta de fazê-lo no Java 8. Collections.sort (list, Comparator.comparing (MyObject :: getStartDate)); que lê melhor e é menos propenso a erros. É muito fácil escrever o retorno o1.getStartDate (). CompareTo (o1.getStartDate ());
Kuba

2
Classe de comparação deve ser estático :)
Jarmez De La Rocha

4
@Kuba melhor ainda, use List.sort().
Shmosel

3
Esta solução não funciona na API do Android <24. Vocês sabem uma solução para isso?
Jim Clermonts

194

As classes que têm uma ordem de classificação natural (um número de classe, por exemplo) devem implementar a interface Comparável, enquanto as classes que não têm uma ordem de classificação natural (uma cadeira de classe, por exemplo) devem receber um comparador (ou um comparador anônimo) classe).

Dois exemplos:

public class Number implements Comparable<Number> {
    private int value;

    public Number(int value) { this.value = value; }
    public int compareTo(Number anotherInstance) {
        return this.value - anotherInstance.value;
    }
}

public class Chair {
    private int weight;
    private int height;

    public Chair(int weight, int height) {
        this.weight = weight;
        this.height = height;
    }
    /* Omitting getters and setters */
}
class ChairWeightComparator implements Comparator<Chair> {
    public int compare(Chair chair1, Chair chair2) {
        return chair1.getWeight() - chair2.getWeight();
    }
}
class ChairHeightComparator implements Comparator<Chair> {
    public int compare(Chair chair1, Chair chair2) {
        return chair1.getHeight() - chair2.getHeight();
    }
}

Uso:

List<Number> numbers = new ArrayList<Number>();
...
Collections.sort(numbers);

List<Chair> chairs = new ArrayList<Chair>();
// Sort by weight:
Collections.sort(chairs, new ChairWeightComparator());
// Sort by height:
Collections.sort(chairs, new ChairHeightComparator());

// You can also create anonymous comparators;
// Sort by color:
Collections.sort(chairs, new Comparator<Chair>() {
    public int compare(Chair chair1, Chair chair2) {
        ...
    }
});

Eu tentei isso - mas quando quero acessar a classe comparadora ChairWeightComparator fora de qualquer outra classe, não tenho acesso a essa classe (é claro que não, pois não é pública). Preciso criar uma nova classe pública ChairWeightComparator em um arquivo separado? - eu sou realmente o primeiro a tentar isso depois de 3 anos ou eu senti falta de sth?
user387184

@ user387184 - basta torná-lo público e colocá-lo em seu próprio arquivo (de preferência também em seu próprio pacote) e você poderá usá-lo em qualquer lugar do seu projeto. Não há necessidade de criar uma classe adicional!
Björn

você quer dizer criar um novo arquivo - não uma classe e inserir o código: "class ChairWeightComparator implementa Comparator <Chair> {...."?
user387184

@ user387184, exatamente - mas com a palavra-chave publicna frente class.
Björn

157

Para classificar um, ArrayListvocê pode usar o seguinte trecho de código:

Collections.sort(studList, new Comparator<Student>(){
    public int compare(Student s1, Student s2) {
        return s1.getFirstName().compareToIgnoreCase(s2.getFirstName());
    }
});

1
Qualquer pessoa usando lambda stackoverflow.com/questions/2784514/…
Classificador

Isso classifica, mas atribui um valor duplo a cada elemento #
Sam

44

Sim você pode. Existem duas opções para comparar itens, a interface Comparável e a interface Comparadora .

Ambas as interfaces permitem um comportamento diferente. Comparable permite que o objeto aja como você acabou de descrever Strings (na verdade, String implementa Comparable). O segundo, Comparador, permite que você faça o que está pedindo. Você faria assim:

Collections.sort(myArrayList, new MyComparator());

Isso fará com que o método Collections.sort use seu comparador para seu mecanismo de classificação. Se os objetos no ArrayList implementarem comparáveis, você poderá fazer algo assim:

Collections.sort(myArrayList);

A classe Coleções contém várias dessas ferramentas comuns úteis.


42

Expressão lambda JAVA 8

Collections.sort(studList, (Student s1, Student s2) ->{
        return s1.getFirstName().compareToIgnoreCase(s2.getFirstName());
});

OU

Comparator<Student> c = (s1, s2) -> s1.firstName.compareTo(s2.firstName);
studList.sort(c)

3
OuCollections.sort(studList, Comparator.comparing(Student::getFirstName));
Holger

5
.. oustudList.sort(Comparator.comparing(Student::getFirstName));
Alexis C.

A ordem de classificação sempre será crescente no seu caso. Eu cuido da ordem de classificação também no meu exemplo. Obrigado, senhores.
Classificador

33

Com o Java 8, você pode usar uma referência de método para o seu comparador:

import static java.util.Comparator.comparing;

Collections.sort(list, comparing(MyObject::getStartDate));

@ user387184 infelizmente, o Android não suporta Java 8, embora possa haver uma solução alternativa (não testei).
assylias 23/03

14

Como as tecnologias aparecem todos os dias, a resposta mudará com o tempo. Dei uma olhada no LambdaJ e parece muito interessante.

Você pode tentar resolver essas tarefas com o LambdaJ . Você pode encontrá-lo aqui: http://code.google.com/p/lambdaj/

Aqui você tem um exemplo:

Classificar Iterativo

List<Person> sortedByAgePersons = new ArrayList<Person>(persons);
Collections.sort(sortedByAgePersons, new Comparator<Person>() {
        public int compare(Person p1, Person p2) {
           return Integer.valueOf(p1.getAge()).compareTo(p2.getAge());
        }
});

Classificar com lambda

List<Person> sortedByAgePersons = sort(persons, on(Person.class).getAge()); 

Obviamente, ter esse tipo de beleza afeta o desempenho (em média 2 vezes), mas você consegue encontrar um código mais legível?


Que tipos, mas dá valor duplo para cada elemento como evitá-la
Sam

@ Sam, não deveria ... está funcionando como esperado. A menos que você esteja usando uma nova versão com um bug, recomendo que você a publique no fórum. Enfim, esta resposta foi posto antes de java 8, se você usá-lo, então ele vai muito melhor do que usar lambdaj
Federico Piazza

Eu tive que remover itens pares em um loop foreach, caso contrário, isso me dá o dobro de cada conteúdo.
Sam

13
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;

public class test {

public static class Person {
    public String name;
    public int id;
    public Date hireDate;

    public Person(String iname, int iid, Date ihireDate) {
        name = iname;
        id = iid;
        hireDate = ihireDate;
    }

    public String toString() {
        return name + " " + id + " " + hireDate.toString();
    }

    // Comparator
    public static class CompId implements Comparator<Person> {
        @Override
        public int compare(Person arg0, Person arg1) {
            return arg0.id - arg1.id;
        }
    }

    public static class CompDate implements Comparator<Person> {
        private int mod = 1;
        public CompDate(boolean desc) {
            if (desc) mod =-1;
        }
        @Override
        public int compare(Person arg0, Person arg1) {
            return mod*arg0.hireDate.compareTo(arg1.hireDate);
        }
    }
}

public static void main(String[] args) {
    // TODO Auto-generated method stub
    SimpleDateFormat df = new SimpleDateFormat("mm-dd-yyyy");
    ArrayList<Person> people;
    people = new ArrayList<Person>();
    try {
        people.add(new Person("Joe", 92422, df.parse("12-12-2010")));
        people.add(new Person("Joef", 24122, df.parse("1-12-2010")));
        people.add(new Person("Joee", 24922, df.parse("12-2-2010")));
    } catch (ParseException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }

    Collections.sort(people, new Person.CompId());
    System.out.println("BY ID");
    for (Person p : people) {
        System.out.println(p.toString());
    }

    Collections.sort(people, new Person.CompDate(false));
    System.out.println("BY Date asc");
    for (Person p : people) {
        System.out.println(p.toString());
    }
    Collections.sort(people, new Person.CompDate(true));
    System.out.println("BY Date desc");
    for (Person p : people) {
        System.out.println(p.toString());
    }

}

}

7
Bem-vindo ao stackoverflow. Esta pergunta foi respondida há algum tempo. Antes de ressuscitar threads antigos, verifique se sua resposta adiciona algo significativo ao thread.
Leigh

1
Por favor, adicione uma explicação à sua resposta.
Arashsoft 01/09/19

Basta compilar e executar. O código é o comentário e a explicação.
CharlesW

9

A melhor maneira fácil com o JAVA 8 é para o tipo alfabético inglês

Implementação de Classe

public class NewspaperClass implements Comparable<NewspaperClass>{
   public String name;

   @Override
   public int compareTo(NewspaperClass another) {
      return name.compareTo(another.name);
   }
}

Ordenar

  Collections.sort(Your List);

Se você deseja classificar para um alfabeto que contenha caracteres que não sejam do inglês, use Localidade ... Abaixo do código, use a classificação de caracteres turcos ...

Implementação de Classe

public class NewspaperClass implements Comparator<NewspaperClass> {
   public String name;
   public Boolean isUserNewspaper=false;
   private Collator trCollator = Collator.getInstance(new Locale("tr_TR"));



   @Override
   public int compare(NewspaperClass lhs, NewspaperClass rhs) {
      trCollator.setStrength(Collator.PRIMARY);
      return trCollator.compare(lhs.name,rhs.name);
   }
}

Ordenar

Collections.sort(your array list,new NewspaperClass());

9

Referência de função e método

O Collections.sortmétodo pode classificar um Listusando um Comparatorpasse. Isso Comparatorpode ser implementado usando o Comparator.comparingmétodo em que você pode passar uma referência de método conforme necessário Function. Felizmente, o código real é muito mais simples e mais curto que esta descrição.

Para Java 8:

Collections.sort(list, comparing(ClassName::getName));

ou

Collections.sort(list, comparing(ClassName::getName).reversed());

Outra maneira é

Collections.sort(list, comparing(ClassName::getName, Comparator.nullsLast(Comparator.naturalOrder())));

1
Chamada exige nível API 24
akshay Bhange


6

O Java 8 Lambda reduz a classificação.

Collections.sort(stdList, (o1, o2) -> o1.getName().compareTo(o2.getName()));

2
Collections.sort(stdList, Comparator.comparing(SomeClass::getName));
bcsb1001


5

Sim, isso é possível, por exemplo, nesta resposta, eu ordeno pela propriedade vda classeIndexValue

    // Sorting by property v using a custom comparator.
    Arrays.sort( array, new Comparator<IndexValue>(){
        public int compare( IndexValue a, IndexValue b ){
            return a.v - b.v;
        }
    });

Se você notar aqui, estou criando uma classe interna anônima (que é o Java para fechamentos) e passando diretamente para o sortmétodo da classeArrays

Seu objeto também pode ser implementado Comparable(é o que String e a maioria das bibliotecas principais em Java fazem), mas isso definiria a "ordem de classificação natural" da classe e não permitirá que você conecte novas.


1
... mas o que você pode simplesmente substituir com Comparator:)
BalusC

5

Descobri que a maioria das respostas, se não todas, se baseia na classe subjacente (Object) para implementar comparável ou ter uma interface comparável auxiliar.

Não com a minha solução! O código a seguir permite comparar o campo do objeto sabendo o nome da sequência. Você pode modificá-lo facilmente para não usar o nome, mas precisa expô-lo ou construir um dos objetos com os quais deseja comparar.

Collections.sort(anArrayListOfSomeObjectPerhapsUsersOrSomething, new ReflectiveComparator(). new ListComparator("name"));

public class ReflectiveComparator {
    public class FieldComparator implements Comparator<Object> {
        private String fieldName;

        public FieldComparator(String fieldName){
            this.fieldName = fieldName;
        }

        @SuppressWarnings({ "unchecked", "rawtypes" })
        @Override
        public int compare(Object object1, Object object2) {
            try {
                Field field = object1.getClass().getDeclaredField(fieldName);
                field.setAccessible(true);

                Comparable object1FieldValue = (Comparable) field.get(object1);
                Comparable object2FieldValue = (Comparable) field.get(object2);

                return object1FieldValue.compareTo(object2FieldValue);
            }catch (Exception e){}

            return 0;
        }
    }

    public class ListComparator implements Comparator<Object> {
        private String fieldName;

        public ListComparator(String fieldName) {
            this.fieldName = fieldName;
        }

        @SuppressWarnings({ "unchecked", "rawtypes" })
        @Override
        public int compare(Object object1, Object object2) {
            try {
                Field field = object1.getClass().getDeclaredField(fieldName);
                field.setAccessible(true);
                Comparable o1FieldValue = (Comparable) field.get(object1);
                Comparable o2FieldValue = (Comparable) field.get(object2);

                if (o1FieldValue == null){ return -1;}
                if (o2FieldValue == null){ return 1;}
                return o1FieldValue.compareTo(o2FieldValue);
            } catch (NoSuchFieldException e) {
                throw new IllegalStateException("Field doesn't exist", e);
            } catch (IllegalAccessException e) {
                throw new IllegalStateException("Field inaccessible", e);
            }
        }
    }
}

5

Você pode tentar o Guava Ordering :

Function<Item, Date> getStartDate = new Function<Item, Date>() {
    public Date apply(Item item) {
        return item.getStartDate();
    }
};

List<Item> orderedItems = Ordering.natural().onResultOf(getStartDate).
                          sortedCopy(items);

5

Você pode classificar usando java 8

yourList.sort(Comparator.comparing(Classname::getName));

or

yourList.stream().forEach(a -> a.getBObjects().sort(Comparator.comparing(Classname::getValue)));

3

Esses trechos de código podem ser úteis. Se você deseja classificar um objeto no meu caso, quero classificar por VolumeName:

public List<Volume> getSortedVolumes() throws SystemException {
    List<Volume> volumes = VolumeLocalServiceUtil.getAllVolumes();
    Collections.sort(volumes, new Comparator<Volume>() {
        public int compare(Volume o1, Volume o2) {
            Volume p1 = (Volume) o1;
            Volume p2 = (Volume) o2;
            return p1.getVolumeName().compareToIgnoreCase(
                    p2.getVolumeName());
        }
    });
    return volumes;
}

Isso funciona. Eu uso no meu jsp.


3

Com esta biblioteca aqui, você pode classificar a lista de objetos personalizados em várias colunas. A biblioteca usa os recursos da versão 8.0. A amostra também está disponível lá. Aqui está uma amostra para fazer

SortKeys sortKeys = new SortKeys();
sortKeys.addField("firstName")
            .addField("age", true); // This (true) will sort the age descending

// Other ways to specify a property to the sorter are
//      .addField("lastName", String.class);
//      .addField("dob", Date.class, true);

// Instantiate a ListSorter
ListSorter listSorter = new ListSorter();

// Pass the data to sort (listToSort) and the "by keys" to sort (sortKeys)
List sortedList = (List<Person>) listSorter.sortList(listToSort, sortKeys);

3

Você pode dar uma olhada nesta apresentação no Java Forum em Stuttgart, Alemanha, em 2016.

Apenas alguns slides usam o idioma alemão, 99% do conteúdo é código-fonte Java "baseado em inglês"; gostar

someCollection.sort(
  OurCustomComparator
    .comparing(Person::getName)
    .thenComparing(Person::getId)
);

Onde OurCustomComparator está usando métodos padrão (e outras idéias interessantes). Como mostrado, levando a um código muito conciso para escolher algum método getter para classificação; e encadeamento super simples (ou reversão) de critérios de classificação.

Se você gosta do java8, encontra muito material para começar.


3

Novo desde 1.8 é um método List.sort () em vez de usar o Collection.sort (), então você chama diretamente mylistcontainer.sort ()

Aqui está um trecho de código que demonstra o recurso List.sort ():

List<Fruit> fruits = new ArrayList<Fruit>();
fruits.add(new Fruit("Kiwi","green",40));
fruits.add(new Fruit("Banana","yellow",100));
fruits.add(new Fruit("Apple","mixed green,red",120));
fruits.add(new Fruit("Cherry","red",10));

// a) using an existing compareto() method
fruits.sort((Fruit f1,Fruit f2) -> f1.getFruitName().compareTo(f2.getFruitName()));
System.out.println("Using String.compareTo(): " + fruits);
//Using String.compareTo(): [Apple is: mixed green,red, Banana is: yellow, Cherry is: red, Kiwi is: green]

// b) Using a comparable class
fruits.sort((Fruit f1,Fruit f2) -> f1.compareTo(f2));  
System.out.println("Using a Comparable Fruit class (sort by color): " + fruits);
// Using a Comparable Fruit class (sort by color): [Kiwi is green, Apple is: mixed green,red, Cherry is: red, Banana is: yellow]

A classe Fruit é:

public class Fruit implements Comparable<Fruit>
{
    private String name;
    private String color;
    private int quantity;

    public Fruit(String name,String color,int quantity)
    { this.name = name; this.color = color; this.quantity = quantity; }

    public String getFruitName() { return name; }        
    public String getColor() { return color; }  
    public int getQuantity() { return quantity; }

    @Override public final int compareTo(Fruit f) // sorting the color
    {
        return this.color.compareTo(f.color);
    }     
    @Override public String toString()
    {   
        return (name + " is: " + color);
    }
} // end of Fruit class   


1

Eu prefiro este processo:

public class SortUtil
{    
    public static <T> List<T> sort(List<T> list, String sortByProperty)
    {
            Collections.sort(list, new BeanComparator(sortByProperty));
            return list;
    }
}

List<T> sortedList = SortUtil<T>.sort(unsortedList, "startDate");

Se você listar objetos com uma propriedade chamada startDate, você pode usar isso repetidamente. Você pode até encadeá-los startDate.time.

Isso requer o seu objeto ser Comparableque significa que você precisa de um compareTo, equalse hashCodeimplementação.

Sim, poderia ser mais rápido ... Mas agora você não precisa fazer um novo Comparador para cada tipo de classificação. Se você pode economizar no tempo de desenvolvimento e desistir do tempo de execução, pode optar por este.


3
1, essa resposta foi dada duas horas antes com o código de trabalho fornecido também. Não há necessidade de repassar a mesma solução e desordenar o fórum, especialmente porque o BeanComparator não é uma classe padrão; portanto, não é realmente uma solução se o pôster não souber do que você está falando. Se você gosta da sugestão original, pode fazer um voto positivo e adicionar um comentário, se desejar.
camickr

0

O uso do Java 8 pode definir o Comparatorem uma linha usandoComparator.comparing()

Use uma das seguintes maneiras:

Opção 1:

listToBeSorted.sort(Comparator.comparing(CustomObject::getStartDate));

Opção 2:

Collections.sort(listToBeSorted, Comparator.comparing(CustomObject::getStartDate));

1
Parece ser uma duplicata desta resposta dos 3 anos anteriores.
Basil Bourque

1
Não exatamente, apenas a opção 2 aqui é semelhante à opção 1 na resposta mencionada. Sinto que duas respostas diferentes podem ter pelo menos algumas semelhanças se forem respostas à mesma pergunta.
Sahil Chhabra

0

Sua classe personalizada pode implementar a interface "Comparável", que requer uma implementação do método CompareTo. No método CompareTo, você pode definir o que significa que um objeto é menor ou maior que o outro objeto. Portanto, no seu exemplo, pode ser algo como isto:

public class MyCustomClass implements Comparable<MyCustomClass>{

..........

 @Override
public int compareTo(MyCustomClass a) {
    if(this.getStartDate().before(a.getStartDate())){
        return -1;
    }else if(a.getStartDate().before(this.getStartDate())){
        return 1;
    }else {
        return 0;
    }
}

Um número negativo indica que este é menor que o objeto que está sendo comparado. Um número positivo indica que isso é maior que o comparado ao objeto e um Zero significa que os objetos são iguais.

Você pode usar o collections.sort (myList) para classificar sua lista sem precisar alimentar um comparador. Esse método também tem a vantagem de classificar as coisas automaticamente, se você usar estruturas de dados de coleção classificada, como um TreeSet ou um TreeMap.

Você pode verificar este artigo se quiser ler mais sobre a interface comparável (divulgação: eu sou o autor;)) https://nullbeans.com/the-java-comparable-interface-automatic-sort-of-collections/


0

Você também pode usar o PropertyComparator do Springs se tiver apenas um caminho de propriedade String para a propriedade (aninhada) que deseja classificar:

List<SomeObject> list = ...;
PropertyComparator<HitWithInfo> propertyComparator = new PropertyComparator<>(
    "property.nested.myProperty", false, true);
list.sort(propertyComparator);

A desvantagem é que esse comparador ignora silenciosamente propriedades que não existem ou não são acessíveis e lida com isso como valor nulo para comparação. Isso significa que você deve testar cuidadosamente esse comparador ou validar a existência do caminho da propriedade de alguma forma.


0

usando a API de fluxo Java-8, você pode classificar ArrayListpor:

 Comparator<Person> birthdayComparator = Comparator.comparing(Person::getBirthday);
 List<Person> sortedList = list.stream().sorted(birthdayComparator).collect(toList());

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.