Supposons que vous ayez certains objets qui ont plusieurs champs, ils peuvent être comparés par:
public class Person {
private String firstName;
private String lastName;
private String age;
/* Constructors */
/* Methods */
}
Donc, dans cet exemple, lorsque vous demandez si:
a.compareTo(b) > 0
vous vous demandez peut-être si le nom de famille de a précède b, ou si a est plus ancien que b, etc ...
Quelle est la façon la plus propre de permettre une comparaison multiple entre ces types d'objets sans ajouter de fouillis ou de frais généraux inutiles?
java.lang.Comparable
l'interface permet la comparaison par un seul champ- L'ajout de nombreuses méthodes de comparaison (ie
compareByFirstName()
,compareByAge()
etc ...) est encombré à mon avis.
Alors, quelle est la meilleure façon de procéder?
Réponses:
Vous pouvez implémenter un
Comparator
qui compare deuxPerson
objets et vous pouvez examiner autant de champs que vous le souhaitez. Vous pouvez mettre une variable dans votre comparateur qui lui indique à quel champ comparer, bien qu'il serait probablement plus simple d'écrire simplement plusieurs comparateurs.la source
Avec Java 8:
Si vous avez des méthodes d'accesseur:
Si une classe implémente Comparable, un tel comparateur peut être utilisé dans la méthode compareTo:
la source
(Person p)
est particulièrement importante pour les comparateurs chaînés.Comparator
instances à chaque appel?.thenComparing(Person::getLastName, Comparator.nullsFirst(Comparator.naturalOrder()))
- premier sélecteur de champ, puis comparateurcompareTo
comme indiqué ci-dessus, leComparator
est créé à chaque appel de la méthode. Vous pouvez empêcher cela en stockant le comparateur dans un champ final statique privé.Vous devez implémenter
Comparable <Person>
. En supposant que tous les champs ne seront pas nuls (par souci de simplicité), que l'âge est un entier et que le classement de comparaison est premier, dernier, âge, lacompareTo
méthode est assez simple:la source
(de façons de trier les listes d'objets en Java en fonction de plusieurs champs )
Code de travail dans cet essentiel
Utilisation des lambda de Java 8 (ajouté le 10 avril 2019)
Java 8 résout bien cela par lambda (bien que Guava et Apache Commons puissent encore offrir plus de flexibilité):
Merci à la réponse de @ gaoagong ci-dessous .
Désordonné et alambiqué: Tri à la main
Cela nécessite beaucoup de frappe, de maintenance et est sujet aux erreurs.
La manière réfléchie: Tri avec BeanComparator
Évidemment, cela est 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-refactorisation). Maintenant, si un champ est renommé, le compilateur ne signalera même pas de problème. De plus, cette solution utilisant la réflexion, le tri est beaucoup plus lent.
Comment y arriver: Trier avec la chaîne de comparaison de Google Guava
C'est beaucoup mieux, mais nécessite un code de plaque de chaudière pour le cas d'utilisation le plus courant: les valeurs nulles doivent être moins valorisées par défaut. Pour les champs nuls, vous devez fournir une directive supplémentaire à Guava que faire dans ce cas. C'est un mécanisme flexible si vous voulez faire quelque chose de spécifique, mais vous voulez souvent le cas par défaut (c'est-à-dire 1, a, b, z, null).
Tri avec Apache Commons CompareToBuilder
Comme ComparisonChain de Guava, cette classe de bibliothèque trie facilement sur plusieurs champs, mais définit également le comportement par défaut pour les valeurs nulles (c'est-à-dire 1, a, b, z, null). Cependant, vous ne pouvez pas spécifier autre chose non plus, sauf si vous fournissez votre propre comparateur.
Donc
En fin de compte, cela se résume à la saveur et au besoin de flexibilité (ComparaisonChain de Guava) par rapport au code concis (CompareToBuilder d'Apache).
Méthode bonus
J'ai trouvé une belle solution qui combine plusieurs comparateurs par ordre de priorité sur CodeReview dans un
MultiComparator
:Ofcourse Apache Commons Collections a déjà une utilité pour cela:
ComparatorUtils.chainedComparator (comparatorCollection)
la source
@Patrick Pour trier plusieurs champs consécutivement, essayez ComparatorChain
la source
Apache Commons est une autre option que vous pouvez toujours envisager. Il offre de nombreuses options.
Ex:
la source
Vous pouvez également consulter Enum qui implémente Comparator.
http://tobega.blogspot.com/2008/05/beautiful-enums.html
par exemple
la source
la source
Pour ceux qui sont capables d'utiliser l'API de streaming Java 8, il existe une approche plus soignée qui est bien documentée ici: Lambdas et le tri
Je cherchais l'équivalent du C # LINQ:
J'ai trouvé le mécanisme en Java 8 sur le comparateur:
Voici donc l'extrait qui illustre l'algorithme.
Consultez le lien ci-dessus pour une manière plus nette et une explication sur la façon dont l'inférence de type Java le rend un peu plus maladroit à définir par rapport à LINQ.
Voici le test unitaire complet pour référence:
la source
Écrire un
Comparator
manuel pour un tel cas d'utilisation est une terrible solution OMI. Ces approches ad hoc présentent de nombreux inconvénients:Alors, quelle est la solution?
D'abord une théorie.
Notons la proposition "type
A
prend en charge la comparaison" parOrd A
. (Du point de vue du programme, vous pouvez penserOrd A
à un objet contenant une logique pour comparer deuxA
s. Oui, tout commeComparator
.)Maintenant, si
Ord A
etOrd B
, alors leur composite(A, B)
devrait également prendre en charge la comparaison. ieOrd (A, B)
. SiOrd A
,Ord B
etOrd C
, alorsOrd (A, B, C)
.Nous pouvons étendre cet argument à l'arité arbitraire et dire:
Ord A, Ord B, Ord C, ..., Ord Z
⇒Ord (A, B, C, .., Z)
Appelons cette déclaration 1.
La comparaison des composites fonctionnera exactement comme vous l'avez décrit dans votre question: la première comparaison sera tentée en premier, puis la suivante, puis la suivante, etc.
C'est la première partie de notre solution. Maintenant la deuxième partie.
Si vous le savez
Ord A
, et savez comment vous transformerB
enA
(appelez cette fonction de transformationf
), alors vous pouvez aussi l'avoirOrd B
. Comment? Eh bien, lorsque les deuxB
instances doivent être comparées, vous les transformez d'abord enA
utilisantf
, puis appliquezOrd A
.Ici, nous mappons la transformation
B → A
versOrd A → Ord B
. Ceci est connu sous le nom de cartographie contravariante (oucomap
pour faire court).Ord A, (B → A)
⇒ comapOrd B
Appelons cette déclaration 2.
Appliquons maintenant ceci à votre exemple.
Vous avez un type de données nommé
Person
qui comprend trois champs de typeString
.Nous le savons
Ord String
. Par déclaration 1,Ord (String, String, String)
.Nous pouvons facilement écrire une fonction de
Person
à(String, String, String)
. (Renvoyez simplement les trois champs.) Puisque nous savonsOrd (String, String, String)
etPerson → (String, String, String)
, par l'énoncé 2, nous pouvons utilisercomap
pour obtenirOrd Person
.QED.
Comment mettre en œuvre tous ces concepts?
La bonne nouvelle, c'est que vous n'avez pas à le faire. Il existe déjà une bibliothèque qui implémente toutes les idées décrites dans cet article. (Si vous êtes curieux de savoir comment ceux-ci sont mis en œuvre, vous pouvez regarder sous le capot .)
Voici à quoi ressemblera le code:
Explication:
stringOrd
est un objet de typeOrd<String>
. Cela correspond à notre proposition originale de "prend en charge la comparaison".p3Ord
est une méthode qui prendOrd<A>
,Ord<B>
,Ord<C>
, et les rendementsOrd<P3<A, B, C>>
. Cela correspond à l'énoncé 1. (P3
signifie produit à trois éléments. Produit est un terme algébrique pour les composites.)comap
correspond biencomap
,.F<A, B>
représente une fonction de transformationA → B
.p
est une méthode d'usine pour créer des produits.J'espère que cela pourra aider.
la source
Au lieu de méthodes de comparaison, vous souhaiterez peut-être simplement définir plusieurs types de sous-classes "Comparateur" à l'intérieur de la classe Person. De cette façon, vous pouvez les transmettre aux méthodes de tri des collections standard.
la source
Je pense que ce serait plus déroutant si votre algorithme de comparaison était "intelligent". J'irais avec les nombreuses méthodes de comparaison que vous avez suggérées.
La seule exception pour moi serait l'égalité. Pour les tests unitaires, il m'a été utile de remplacer les .Equals (en .net) afin de déterminer si plusieurs champs sont égaux entre deux objets (et non pas que les références soient égales).
la source
S'il existe plusieurs façons pour un utilisateur de commander une personne, vous pouvez également configurer plusieurs comparateurs sous forme de constantes quelque part. La plupart des opérations de tri et des collections triées prennent un comparateur comme paramètre.
la source
la source
L'implémentation du code est la même si nous devons trier l'objet Person en fonction de plusieurs champs.
la source
la source
Si vous implémentez l' interface Comparable , vous souhaiterez choisir une propriété simple à classer. C'est ce qu'on appelle l'ordre naturel. Considérez-le comme la valeur par défaut. Il est toujours utilisé quand aucun comparateur spécifique n'est fourni. Il s'agit généralement d'un nom, mais votre cas d'utilisation peut nécessiter quelque chose de différent. Vous êtes libre d'utiliser n'importe quel nombre d'autres comparateurs que vous pouvez fournir à diverses API de collections pour remplacer l'ordre naturel.
Notez également que généralement si a.compareTo (b) == 0, alors a.equals (b) == true. Ce n'est pas grave, mais il y a des effets secondaires à connaître. Voir les excellents javadocs sur l'interface Comparable et vous trouverez beaucoup d'informations intéressantes à ce sujet.
la source
Blog suivant donné un bon exemple de comparateur enchaîné
http://www.codejava.net/java-core/collections/sorting-a-list-by-multiple-attributes-example
Comparateur d'appel:
la source
À partir de la réponse de Steve, l'opérateur ternaire peut être utilisé:
la source
Il est facile de comparer deux objets avec la méthode hashcode en java`
la source
Habituellement, je remplace ma
compareTo()
méthode comme celle-ci chaque fois que je dois faire un tri à plusieurs niveaux.Ici, la première préférence est donnée au nom du film, puis à l'artiste et enfin à SongLength. Il vous suffit de vous assurer que ces multiplicateurs sont suffisamment éloignés pour ne pas franchir les frontières les uns des autres.
la source
Son facile à faire en utilisant la bibliothèque de Google Goyave .
par exemple
Objects.equal(name, name2) && Objects.equal(age, age2) && ...
Plus d'exemples:
la source