(originellement de Façons de trier des listes d'objets en Java sur la base de plusieurs champs )
Code de travail original dans cette phrase
Utiliser les lambdas de Java 8 (ajouté le 10 avril 2019)
Java 8 résout joliment ce problème grâce aux lambdas (bien que Guava et Apache Commons puissent encore offrir plus de flexibilité) :
Collections.sort(reportList, Comparator.comparing(Report::getReportKey)
.thenComparing(Report::getStudentNumber)
.thenComparing(Report::getSchool));
Grâce à @gaoagong's réponse ci-dessous .
Notez qu'un avantage ici est que les getters sont évalués paresseusement (ex. getSchool()
n'est évaluée que si elle est pertinente).
Désordonné et alambiqué : Triage à la main
Collections.sort(pizzas, new Comparator<Pizza>() {
@Override
public int compare(Pizza p1, Pizza p2) {
int sizeCmp = p1.size.compareTo(p2.size);
if (sizeCmp != 0) {
return sizeCmp;
}
int nrOfToppingsCmp = p1.nrOfToppings.compareTo(p2.nrOfToppings);
if (nrOfToppingsCmp != 0) {
return nrOfToppingsCmp;
}
return p1.name.compareTo(p2.name);
}
});
Cela nécessite beaucoup de saisie, de maintenance et est sujet à des erreurs. Le seul avantage est que getters ne sont invoquées que lorsqu'elles sont pertinentes.
La voie de la réflexion : Triage avec BeanComparator
ComparatorChain chain = new ComparatorChain(Arrays.asList(
new BeanComparator("size"),
new BeanComparator("nrOfToppings"),
new BeanComparator("name")));
Collections.sort(pizzas, chain);
C'est évidemment plus concis, mais encore plus sujet aux erreurs car vous perdez votre référence directe aux champs en utilisant des chaînes à la place (pas de sécurité de type, auto-réfactorisations). Maintenant, si un champ est renommé, le compilateur ne signalera même pas de problème. De plus, comme cette solution utilise la réflexion, le tri est beaucoup plus lent.
Pour s'y rendre : Trier avec la chaîne de comparaison de Google Guava
Collections.sort(pizzas, new Comparator<Pizza>() {
@Override
public int compare(Pizza p1, Pizza p2) {
return ComparisonChain.start().compare(p1.size, p2.size).compare(p1.nrOfToppings, p2.nrOfToppings).compare(p1.name, p2.name).result();
// or in case the fields can be null:
/*
return ComparisonChain.start()
.compare(p1.size, p2.size, Ordering.natural().nullsLast())
.compare(p1.nrOfToppings, p2.nrOfToppings, Ordering.natural().nullsLast())
.compare(p1.name, p2.name, Ordering.natural().nullsLast())
.result();
*/
}
});
C'est beaucoup mieux, mais cela nécessite un code passe-partout pour le cas d'utilisation le plus courant : les valeurs nulles doivent avoir une valeur moindre par défaut. Pour les champs nuls, vous devez fournir une directive supplémentaire à Guava sur ce qu'il faut faire dans ce cas. C'est un mécanisme flexible si vous voulez faire quelque chose de spécifique, mais souvent vous voulez le cas par défaut (c'est-à-dire 1, a, b, z, null).
Et comme indiqué dans les commentaires ci-dessous, ces getters sont tous évalués immédiatement pour chaque comparaison.
Triage avec Apache Commons CompareToBuilder
Collections.sort(pizzas, new Comparator<Pizza>() {
@Override
public int compare(Pizza p1, Pizza p2) {
return new CompareToBuilder().append(p1.size, p2.size).append(p1.nrOfToppings, p2.nrOfToppings).append(p1.name, p2.name).toComparison();
}
});
Comme ComparisonChain de Guava, cette classe de bibliothèque trie facilement sur plusieurs champs, mais définit également un comportement par défaut pour les valeurs nulles (c'est-à-dire 1, a, b, z, null). Cependant, vous ne pouvez pas non plus spécifier autre chose, sauf si vous fournissez votre propre comparateur.
Encore une fois, comme indiqué dans les commentaires ci-dessous, ces getters sont tous évalués immédiatement pour chaque comparaison.
Ainsi,
En fin de compte, c'est une question de goût et de besoin de flexibilité (ComparisonChain de Guava) par rapport à un code concis (CompareToBuilder d'Apache).
Méthode de bonus
J'ai trouvé une solution intéressante qui combine plusieurs comparateurs par ordre de priorité. sur CodeReview dans un MultiComparator
:
class MultiComparator<T> implements Comparator<T> {
private final List<Comparator<T>> comparators;
public MultiComparator(List<Comparator<? super T>> comparators) {
this.comparators = comparators;
}
public MultiComparator(Comparator<? super T>... comparators) {
this(Arrays.asList(comparators));
}
public int compare(T o1, T o2) {
for (Comparator<T> c : comparators) {
int result = c.compare(o1, o2);
if (result != 0) {
return result;
}
}
return 0;
}
public static <T> void sort(List<T> list, Comparator<? super T>... comparators) {
Collections.sort(list, new MultiComparator<T>(comparators));
}
}
Bien sûr, Apache Commons Collections dispose déjà d'un utilitaire pour cela :
ComparatorUtils.chainedComparator(comparatorCollection)
Collections.sort(list, ComparatorUtils.chainedComparator(comparators));
0 votes
S'agit-il de champs de longueur fixe ? Que se passe-t-il si record1.getReportKey() est "AB" et record1.getStudentNumber() est "CD", mais que record2.getReportKey() est "ABCD" ?
0 votes
Longueur fixe. Désolé, j'ai oublié de le mentionner.
0 votes
Duplicata possible de Meilleur moyen de comparer des objets par plusieurs champs ?