Trier une liste d'objets à l'aide d'un ordre de tri personnalisé


119

Je cherche à implémenter une fonction de tri pour mon application de carnet d'adresses.

Je veux trier un fichier ArrayList<Contact> contactArray. Contactest une classe qui contient quatre champs: nom, numéro de domicile, numéro de portable et adresse. Je veux trier name.

Comment puis-je écrire une fonction de tri personnalisée pour ce faire?

Réponses:


270

Voici un tutoriel sur l'ordre des objets:

Bien que je donne quelques exemples, je recommanderais quand même de le lire.


Il existe différentes manières de trier un fichier ArrayList. Si vous souhaitez définir un naturel (par défaut) commande , vous devez laisser Contactmettre en œuvre Comparable. En supposant que vous vouliez trier par défaut name, alors faites (nullchecks omis pour plus de simplicité):

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

pour que tu puisses faire

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

Collections.sort(contacts);

Si vous souhaitez définir un ordre contrôlable externe (qui remplace l'ordre naturel), vous devez créer un 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());
    }
}); 

Vous pouvez même définir le Comparators Contactlui - même afin de pouvoir les réutiliser au lieu de les recréer à chaque fois:

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

}

qui peut être utilisé comme suit:

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

Et pour couronner le tout, vous pouvez envisager d'utiliser un comparateur générique 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));
    }

}

que vous pouvez utiliser comme suit:

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

(comme vous le voyez dans le code, les champs éventuellement nuls sont déjà couverts pour éviter les NPE pendant le tri)


2
J'ajouterais la possibilité de prédéfinir plusieurs comparateurs, puis de les utiliser par leur nom ...
Stobor

2
En fait, je viens de le faire. Plus facile que d'essayer de m'expliquer.
Stobor

@BalusC: Aucun problème. Je ne peux pas m'attribuer le mérite de l'idée, je l'ai eue de mes String.CASE_INSENSITIVE_ORDERamis mais j'aime ça. Rend le code résultant plus facile à lire.
Stobor

1
Ces définitions de Comparator devraient probablement être aussi staticet peut-être finalaussi ... Ou quelque chose comme ça ..
Stobor

Heheh ... BeanComparator est comme Awesome On A Stick! :-) (Je ne me souviens pas de la comparaison logique exacte des valeurs nulles, mais veut-il un (o1 == null && o2 == null) ? 0 :au début de cette ligne de retour?)
Stobor

28

En plus de ce qui a déjà été publié, sachez que depuis Java 8, nous pouvons raccourcir notre code et l'écrire comme:

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

ou puisque List a maintenant la sortméthode

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

Explication:

Depuis Java 8, les interfaces fonctionnelles (interfaces avec une seule méthode abstraite - elles peuvent avoir plus de méthodes par défaut ou statiques) peuvent être facilement implémentées en utilisant:

Depuis Comparator<T>n'a qu'une seule méthode abstraite, int compare(T o1, T o2)c'est une interface fonctionnelle.

Donc au lieu de (exemple de la réponse @BalusC )

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

nous pouvons réduire ce code à:

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

Nous pouvons simplifier ceci (ou tout autre) lambda en sautant

  • types d'arguments (Java les déduira en fonction de la signature de la méthode)
  • ou {return...}

Donc au lieu de

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

nous pouvons écrire

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

Il Comparatora également maintenant des méthodes statiques comme comparing(FunctionToComparableValue)ou comparing(FunctionToValue, ValueComparator)que nous pourrions utiliser pour créer facilement des comparateurs qui devraient comparer certaines valeurs spécifiques d'objets.

En d'autres termes, nous pouvons réécrire le code ci-dessus comme

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

8

Cette page vous indique tout ce que vous devez savoir sur le tri des collections, telles que ArrayList.

Fondamentalement, vous devez

  • faites en sorte que votre Contactclasse implémente l' Comparableinterface en
    • créer une méthode public int compareTo(Contact anotherContact)en son sein.
  • Une fois que vous faites cela, vous pouvez simplement appeler Collections.sort(myContactList);,
    • myContactListest ArrayList<Contact>(ou toute autre collection de Contact).

Il existe également un autre moyen, consistant à créer une classe Comparator, et vous pouvez également en savoir plus sur la page liée.

Exemple:

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 et bguiz ont déjà donné des réponses très complètes sur la façon d'utiliser les comparateurs intégrés de Java.

Je veux juste ajouter que google-collections a une classe Ordering qui est plus "puissante" que les comparateurs standard. Cela vaut peut-être la peine de vérifier. Vous pouvez faire des choses sympas telles que la composition des commandes, les inverser, les ordonner en fonction du résultat d'une fonction pour vos objets ...

Voici un article de blog qui mentionne certains de ses avantages.


Notez que google-collections fait maintenant partie de Guava (les bibliothèques java communes de Google), vous pouvez donc dépendre de Guava (ou du module de collection de Guava) si vous souhaitez utiliser la classe Ordering.
Etienne Neveu

4

Vous devez faire en sorte que vos classes Contact implémentent Comparable , puis implémenter la compareTo(Contact)méthode. De cette façon, Collections.sort pourra les trier pour vous. Selon la page à laquelle j'ai lié, compareTo 'renvoie un entier négatif, zéro ou un entier positif car cet objet est inférieur, égal ou supérieur à l'objet spécifié.'

Par exemple, si vous vouliez trier par nom (de A à Z), votre classe ressemblerait à ceci:

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

A bien travaillé avec moi, merci! J'ai également utilisé compareToIgnoreCase pour ignorer la casse.
Rani Kheir

3

En utilisant lambdaj, vous pouvez trier une collection de vos contacts (par exemple par leur nom) comme suit

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

ou par leur adresse:

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

etc. Plus généralement, il propose un DSL pour accéder et manipuler vos collections de plusieurs manières, comme filtrer ou regrouper vos contacts en fonction de certaines conditions, agréger certaines de leurs valeurs de propriété, etc.


0

Collections.sort est une bonne implémentation de tri. Si vous n'avez pas implémenté The comparable pour Contact, vous devrez passer une implémentation de Comparator

À noter:

L'algorithme de tri est un tri de fusion modifié (dans lequel la fusion est omise si l'élément le plus élevé de la sous-liste inférieure est inférieur à l'élément le plus bas de la sous-liste supérieure). Cet algorithme offre des performances garanties n log (n). La liste spécifiée doit être modifiable, mais pas nécessairement redimensionnable. Cette implémentation vide la liste spécifiée dans un tableau, trie le tableau et itère sur la liste en réinitialisant chaque élément à partir de la position correspondante dans le tableau. Cela évite les performances de n2 log (n) qui résulteraient de la tentative de tri d'une liste liée sur place.

Le tri par fusion est probablement meilleur que la plupart des algorithmes de recherche que vous pouvez faire.


0

Je l'ai fait de la manière suivante. le nombre et le nom sont deux arraylist. Je dois trier le nom .Si un changement se produit dans l'ordre des noms d'arralistes, alors la liste des nombres change également son ordre.

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


                        }
                    }
                }
            }



}

Vous allez prendre plus de temps pour écrire ceci, obtenir des performances de tri moins optimales, écrire plus de bogues (et, espérons-le, plus de tests), et le code sera plus difficile à transférer à d'autres personnes. Donc ce n'est pas juste. Cela peut fonctionner, mais cela ne le rend pas correct.
Eric

0

utilisez cette méthode:

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

»

et utilisation: mySortedlist = sortList(myList); pas besoin d'implémenter le comparateur dans votre classe. Si vous voulez un échange d'ordre inverse 1et-1


0

Ok, je sais que cela a été répondu il y a longtemps ... mais voici quelques nouvelles informations:

Supposons que la classe Contact en question ait déjà un ordre naturel défini via l'implémentation de Comparable, mais que vous souhaitiez remplacer cet ordre, par exemple par son nom. Voici la façon moderne de le faire:

List<Contact> contacts = ...;

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

De cette façon, il triera d'abord par nom (dans l'ordre inverse), puis pour les collisions de noms, il reviendra à l'ordre 'naturel' implémenté par la classe Contact elle-même.


-1

Vous devez utiliser la fonction Arrays.sort. Les classes contenant doivent implémenter Comparable.


C'est pourquoi j'ai dit Arrays.
monksy

Le problème est qu'OP utilise ArrayList, pas un tableau.
Pshemo
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.