Comment trier la liste des objets par une propriété

145

J'ai une classe simple

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

et List<ActiveAlarm>con. Comment trier par ordre croissant timeStarted, puis par timeEnded? Quelqu'un peut-il aider? Je connais en C ++ avec un algorithme générique et un opérateur de surcharge <, mais je suis nouveau sur Java.

Jennifer
la source
La réponse de @Yishai dans cet article démontre l'utilisation élégante d'énumération pour le tri personnalisé et le tri groupé (arguments multiples) en utilisant le chaînage de comparateurs.
gunalmel

Réponses:

141

Faites ActiveAlarmimplémenter Comparable<ActiveAlarm>ou implémentez Comparator<ActiveAlarm>dans une classe distincte. Puis appelez:

Collections.sort(list);

ou

Collections.sort(list, comparator);

En général, il est une bonne idée de mettre en œuvre Comparable<T>s'il y a un ordre de tri « naturel » ... sinon (si vous arrive de vouloir trier dans un ordre particulier, mais pourrait vouloir aussi facilement un différent) il est préférable de mettre en œuvre Comparator<T>. Cette situation particulière pourrait aller dans les deux sens, pour être honnête ... mais je resterais probablement avec l' Comparator<T>option la plus flexible .

EDIT: Exemple d'implémentation:

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
la source
1
Cette fonction compare () n'est probablement pas dans Long car l'implémentation est encore plus triviale: return a - b;
papercrane
5
@papercrane: Non, cela échoue pour des raisons de débordement. Considérez a = Long.MIN_VALUE, b = 1.
Jon Skeet
3
à partir de l'API 19 (KitKat) Long a maintenant un.compare
Martin Marconcini
123

En utilisant Comparator

Par exemple:

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

À partir de Java 8, vous pouvez simplement utiliser une expression lambda pour représenter l'instance de Comparator.

Collections.sort(scores, (s1, s2) -> { /* compute and return int */ });
Jigar Joshi
la source
1
Que fait compareTo()-on? D'où est ce que ça vient? Où dois-je le définir?
Asqiir
1
@Asqiir getScores()est le getter pour scoreslequel est un List<Integer>. Lorsque vous getScores().get(0)obtenez un Integerobjet. Integera déjà la compareTo(anotherInteger)méthode implémentée, vous n'avez pas à la définir.
walen
qu'est-ce que get (0)?
Ali Khaki
1
Merci fonctionne comme un charme (je l'utilise sans la partie .get (0)). Comment pourrais-je inverser la commande?
53

Réponse JAVA 8 et ci-dessus (à l'aide d'expressions Lambda)

Dans Java 8, des expressions Lambda ont été introduites pour rendre cela encore plus facile! Au lieu de créer un objet Comparator () avec tout son échafaudage, vous pouvez le simplifier comme suit: (en utilisant votre objet comme exemple)

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

ou même plus court:

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

Cette déclaration équivaut à ce qui suit:

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

Considérez les expressions Lambda comme vous obligeant uniquement à insérer les parties pertinentes du code: la signature de la méthode et ce qui est renvoyé.

Une autre partie de votre question était de savoir comment comparer avec plusieurs champs. Pour ce faire avec les expressions Lambda, vous pouvez utiliser la .thenComparing()fonction pour combiner efficacement deux comparaisons en une:

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

Le code ci-dessus triera la liste d'abord par timeStarted, puis par timeEnded(pour les enregistrements qui ont le même timeStarted).

Une dernière remarque: il est facile de comparer les primitives «long» ou «int», vous pouvez simplement soustraire l'une de l'autre. Si vous comparez des objets («Long» ou «String»), je vous suggère d'utiliser leur comparaison intégrée. Exemple:

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

EDIT: Merci à Lukas Eder de m'avoir indiqué .thenComparing()fonctionner.

John Fowler
la source
4
Vous apprécierez peut-être la nouvelle API Java 8 Comparator.comparing().thenComparing()...
Lukas Eder
1
Attention à la différence de retour. En cas de débordement, vous pouvez obtenir un résultat erroné.
krzychu
2
C'est à mon humble avis la méthode de tri la plus simple, en supposant encore une fois que vous n'avez pas un ordre naturel évident. Vous n'avez plus besoin d'appeler non Collectionsplus, vous pouvez appeler directement sur la liste. Par exemple:myList.sort(Comparator.comparing(Address::getZipCode).thenComparing(Compartor.comparing(Address::getStreetName));
CeePlusPlus
20

Nous pouvons trier la liste de deux manières:

1. Utilisation du comparateur : lorsque vous devez utiliser la logique de tri à plusieurs endroits Si vous souhaitez utiliser la logique de tri à un seul endroit, vous pouvez écrire une classe interne anonyme comme suit, ou bien extraire le comparateur et l'utiliser à plusieurs endroits

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

Nous pouvons avoir une vérification nulle pour les propriétés, si nous aurions pu utiliser «Long» au lieu de «long».

2. Utilisation de Comparable (ordre naturel) : Si l'algorithme de tri s'en tient toujours à une propriété: écrivez une classe qui implémente 'Comparable' et remplacez la méthode 'compareTo' comme défini ci-dessous

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

}

appelez la méthode de tri pour trier en fonction de l'ordre naturel

Collections.sort(list);
Jagadeesh
la source
7

En java8 +, cela peut être écrit en une seule ligne comme suit:

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

code:

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

ou

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

Cela devrait vous donner une idée approximative. Une fois cela fait, vous pouvez appeler Collections.sort()sur la liste.

Kal
la source
4

Depuis Java8, cela peut être fait encore plus proprement en utilisant une combinaison de ComparatoretLambda expressions

Par exemple:

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.
la source
2

De goyave 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
la source
1

En java, vous devez utiliser la Collections.sortméthode statique . Voici un exemple de liste d'objets CompanyRole, triés d'abord par begin, puis par end. Vous pouvez facilement vous adapter à votre propre objet.

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
la source
0

Vous pouvez appeler Collections.sort () et transmettre un comparateur que vous devez écrire pour comparer différentes propriétés de l'objet.

Liv
la source
0

Comme mentionné, vous pouvez trier par:

  • Faire implémenter votre objet Comparable
  • Ou passer un ComparatoràCollections.sort

Si vous faites les deux, le Comparablesera ignoré et Comparatorsera utilisé. Cela permet que les objets de valeur aient leur propre logique, Comparablequi est le tri le plus raisonnable pour votre objet de valeur, tandis que chaque cas d'utilisation individuel a sa propre implémentation.

Alireza Fattahi
la source