Classificando uma ArrayList de objetos usando uma ordem de classificação personalizada


119

Estou procurando implementar um recurso de classificação para meu aplicativo de catálogo de endereços.

Eu quero classificar um ArrayList<Contact> contactArray. Contacté uma classe que contém quatro campos: nome, número residencial, número de celular e endereço. Eu quero resolvername .

Como posso escrever uma função de classificação personalizada para fazer isso?

Respostas:


270

Aqui está um tutorial sobre como solicitar objetos:

Embora eu dê alguns exemplos, recomendo lê-lo de qualquer maneira.


Existem várias maneiras de classificar um ArrayList. Se você quiser definir um naturais (default) de pedidos , então você precisa deixar Contactimplementar Comparable. Supondo que você deseja classificar por padrão name, faça (nullchecks omitidos para simplificar):

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

para que você possa apenas fazer

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

Collections.sort(contacts);

Se você deseja definir uma ordem controlável externa (que substitui a ordem natural), você precisa criar um 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());
    }
}); 

Você pode até definir os Comparators em Contactsi para que possa reutilizá-los em vez de recriá-los sempre:

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

}

que pode ser usado da seguinte forma:

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

E para limpar a cobertura, você pode considerar o uso de um comparador JavaBeano genérico :

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

}

que você pode usar da seguinte maneira:

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

(como você vê no código, possivelmente campos nulos já estão cobertos para evitar NPE durante a classificação)


2
Eu adicionaria a possibilidade de predefinir vários comparadores e, em seguida, usá-los pelo nome ...
Stobor

2
Na verdade, acabei de fazer. Mais fácil do que tentar me explicar.
Stobor

@BalusC: Sem probs. Não posso levar o crédito pela ideia, eu herdei String.CASE_INSENSITIVE_ORDERe de amigos, mas gosto. Torna o código resultante mais fácil de ler.
Stobor

1
Essas definições de Comparador provavelmente também deveriam ser statice talvez finaltambém ... Ou algo assim ..
Stobor

Heheh ... BeanComparator é como Awesome On A Stick! :-) (Não me lembro da comparação lógica exata de nulos, mas ele quer um (o1 == null && o2 == null) ? 0 :no início dessa linha de retorno?)
Stobor

28

Além do que já foi postado, você deve saber que desde Java 8 podemos encurtar nosso código e escrevê-lo assim:

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

ou já que Lista agora tem sortmétodo

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

Explicação:

Desde o Java 8, as interfaces funcionais (interfaces com apenas um método abstrato - podem ter mais métodos padrão ou estáticos) podem ser facilmente implementadas usando:

Como Comparator<T>tem apenas um método abstrato int compare(T o1, T o2), é uma interface funcional.

Então, em vez de (exemplo da resposta @BalusC )

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

podemos reduzir esse código a:

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

Podemos simplificar este (ou qualquer) lambda pulando

  • tipos de argumento (Java os inferirá com base na assinatura do método)
  • ou {return...}

Então, em vez de

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

nós podemos escrever

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

Também agora Comparatortem métodos estáticos como comparing(FunctionToComparableValue)oucomparing(FunctionToValue, ValueComparator) que podemos usar para criar facilmente comparadores que devem comparar alguns valores específicos de objetos.

Em outras palavras, podemos reescrever o código acima como

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

8

Esta página mostra tudo o que você precisa saber sobre a classificação de coleções, como ArrayList.

Basicamente, você precisa

  • faça sua Contactclasse implementar a Comparableinterface
    • criando um método public int compareTo(Contact anotherContact)dentro dele.
  • Depois de fazer isso, você pode simplesmente ligar Collections.sort(myContactList);,
    • onde myContactListestá ArrayList<Contact>(ou qualquer outra coleção de Contact).

Também há outra maneira, envolvendo a criação de uma classe Comparator, e você também pode ler sobre isso na página vinculada.

Exemplo:

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 e bguiz já deram respostas muito completas sobre como usar os comparadores integrados do Java.

Só quero acrescentar que google-Collections tem uma classe Ordering que é mais "poderosa" do que os Comparators padrão. Pode valer a pena conferir. Você pode fazer coisas interessantes, como combinar ordens, invertê-las, ordenar dependendo do resultado de uma função para seus objetos ...

Aqui está uma postagem de blog que menciona alguns de seus benefícios.


Observe que google-Collections agora faz parte do Guava (bibliotecas Java comuns do Google), então você pode querer depender do Guava (ou módulo de coleção do Guava) se quiser usar a classe Ordering.
Etienne Neveu

4

Você precisa fazer com que suas classes de contato implementem Comparable e, em seguida, implemente ocompareTo(Contact) método. Dessa forma, o Collections.sort poderá classificá-los para você. De acordo com a página que vinculei, compareTo 'retorna um inteiro negativo, zero ou um inteiro positivo, pois este objeto é menor, igual ou maior que o objeto especificado.'

Por exemplo, se você quiser classificar por nome (A a Z), sua classe ficaria assim:

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

Funcionou bem comigo, obrigado! Eu também usei compareToIgnoreCase para ignorar maiúsculas e minúsculas.
Rani Kheir de

3

Usando lambdaj, você pode classificar uma coleção de seus contatos (por exemplo, por seus nomes) da seguinte forma

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

ou pelo endereço:

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

e assim por diante. Em geral, oferece uma DSL para acessar e manipular suas coleções de várias maneiras, como filtrar ou agrupar seus contatos com base em algumas condições, agregar alguns de seus valores de propriedade, etc.


0

O Collections.sort é uma boa implementação de classificação. Se você não tiver o comparável implementado para o contato, precisará passar em uma implementação do comparador

De importância:

O algoritmo de classificação é um mergesort modificado (no qual a fusão é omitida se o elemento mais alto na sublista inferior for menor do que o elemento mais baixo na sublista alta). Este algoritmo oferece desempenho n log (n) garantido. A lista especificada deve ser modificável, mas não precisa ser redimensionável. Essa implementação despeja a lista especificada em uma matriz, classifica a matriz e itera sobre a lista, redefinindo cada elemento da posição correspondente na matriz. Isso evita o desempenho n2 log (n) que resultaria da tentativa de classificar uma lista vinculada no local.

A classificação de mesclagem é provavelmente melhor do que a maioria dos algoritmos de pesquisa que você pode fazer.


0

Eu fiz da seguinte maneira. número e nome são dois arraylist. Eu tenho que classificar o nome. Se qualquer mudança acontecer na ordem do nome arralista, o número arraylist também mudará sua ordem.

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


                        }
                    }
                }
            }



}

Você vai demorar mais para escrever isso, obter um desempenho de classificação menos ideal, escrever mais bugs (e, com sorte, mais testes), e o código será mais difícil de transferir para outras pessoas. Então não está certo. Pode funcionar, mas isso não o torna correto.
Eric

0

use este método:

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

`

e use: mySortedlist = sortList(myList); Não há necessidade de implementar comparador em sua classe. Se você deseja troca de ordem inversa 1e-1


0

Ok, sei que isso foi respondido há muito tempo ... mas, aqui estão algumas informações novas:

Digamos que a classe Contact em questão já tenha uma ordem natural definida por meio da implementação de Comparable, mas você deseja substituir essa ordem, digamos pelo nome. Esta é a maneira moderna de fazer isso:

List<Contact> contacts = ...;

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

Dessa forma, ele irá classificar por nome primeiro (na ordem reversa) e, em seguida, para colisões de nomes, ele retornará à ordem 'natural' implementada pela própria classe Contact.


-1

Você deve usar a função Arrays.sort. As classes de conteúdo devem implementar Comparable.


É por isso que eu disse Arrays.
monksy

O problema é que o OP está usando ArrayList, não array.
Pshemo de
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.