Suponga que tiene algunos objetos que tienen varios campos con los que se pueden comparar:
public class Person {
private String firstName;
private String lastName;
private String age;
/* Constructors */
/* Methods */
}
Entonces, en este ejemplo, cuando preguntas si:
a.compareTo(b) > 0
Tal vez se pregunte si el apellido de a viene antes que el de b, o si a es anterior a b, etc.
¿Cuál es la forma más limpia de permitir la comparación múltiple entre este tipo de objetos sin agregar desorden innecesario o sobrecarga?
java.lang.Comparable
la interfaz permite la comparación solo por un campo- Agregar numerosos métodos de comparación (es decir
compareByFirstName()
,compareByAge()
etc.) está abarrotado en mi opinión.
Entonces, ¿cuál es la mejor manera de hacerlo?
Respuestas:
Puede implementar un
Comparator
que compara dosPerson
objetos, y puede examinar tantos campos como desee. Puede poner una variable en su comparador que le indique con qué campo comparar, aunque probablemente sería más simple escribir comparadores múltiples.fuente
Con Java 8:
Si tiene métodos de acceso:
Si una clase implementa Comparable, dicho comparador puede usarse en el método compareTo:
fuente
(Person p)
es importante para los comparadores encadenados.Comparator
instancias en cada llamada?.thenComparing(Person::getLastName, Comparator.nullsFirst(Comparator.naturalOrder()))
- primer selector de campo, luego comparadorcompareTo
como se muestra arriba,Comparator
se crea cada vez que se llama al método. Puede evitar esto almacenando el comparador en un campo final estático privado.Deberías implementar
Comparable <Person>
. Suponiendo que todos los campos no serán nulos (por simplicidad), esa edad es un int, y comparar la clasificación es primero, último, edad, elcompareTo
método es bastante simple:fuente
(desde Formas de ordenar listas de objetos en Java en función de múltiples campos )
Código de trabajo en esta esencia
Uso de Java 8 lambda (agregado el 10 de abril de 2019)
Java 8 resuelve esto muy bien por lambda (aunque Guava y Apache Commons aún podrían ofrecer más flexibilidad):
Gracias a la respuesta de @ gaoagong a continuación .
Desordenado y complicado: ordenar a mano
Esto requiere mucho tipeo, mantenimiento y es propenso a errores.
La forma reflexiva: ordenar con BeanComparator
Obviamente, esto es más conciso, pero aún más propenso a errores, ya que pierde su referencia directa a los campos mediante el uso de cadenas (sin seguridad de tipografía, refactorizaciones automáticas). Ahora, si se cambia el nombre de un campo, el compilador ni siquiera informará un problema. Además, debido a que esta solución utiliza la reflexión, la clasificación es mucho más lenta.
Cómo llegar: Ordenando con Google Guava's ComparisonChain
Esto es mucho mejor, pero requiere un código de placa de caldera para el caso de uso más común: los valores nulos deben valorarse menos por defecto. Para los campos nulos, debe proporcionar una directiva adicional a Guava sobre qué hacer en ese caso. Este es un mecanismo flexible si desea hacer algo específico, pero a menudo desea el caso predeterminado (es decir, 1, a, b, z, nulo).
Ordenar con Apache Commons CompareToBuilder
Al igual que ComparisonChain de Guava, esta clase de biblioteca se clasifica fácilmente en múltiples campos, pero también define el comportamiento predeterminado para valores nulos (es decir, 1, a, b, z, nulo). Sin embargo, tampoco puede especificar nada más, a menos que proporcione su propio Comparador.
Así
En última instancia, se trata de sabor y la necesidad de flexibilidad (Guava's ComparisonChain) versus código conciso (Apache's CompareToBuilder).
Método de bonificación
Encontré una buena solución que combina múltiples comparadores en orden de prioridad en CodeReview en
MultiComparator
:Por supuesto, Apache Commons Collections ya tiene una utilidad para esto:
ComparatorUtils.chainedComparator (comparatorCollection)
fuente
@Patrick Para ordenar más de un campo consecutivamente intente ComparatorChain
fuente
Otra opción que siempre puede considerar es Apache Commons. Proporciona muchas opciones.
Ex:
fuente
También puede echar un vistazo a Enum que implementa Comparator.
http://tobega.blogspot.com/2008/05/beautiful-enums.html
p.ej
fuente
fuente
Para aquellos que pueden usar la API de transmisión Java 8, hay un enfoque más ordenado que está bien documentado aquí: Lambdas y clasificación
Estaba buscando el equivalente de C # LINQ:
Encontré el mecanismo en Java 8 en el Comparador:
Así que aquí está el fragmento que demuestra el algoritmo.
Consulte el enlace de arriba para obtener una forma más ordenada y una explicación sobre cómo la inferencia de tipos de Java hace que sea un poco más difícil de definir en comparación con LINQ.
Aquí está la prueba de unidad completa para referencia:
fuente
Escribir un
Comparator
manual para tal caso de uso es una terrible solución IMO. Dichos enfoques ad hoc tienen muchos inconvenientes:Entonces, ¿cuál es la solución?
Primero algo de teoría.
Denotemos la proposición "tipo
A
admite comparación" porOrd A
. (Desde la perspectiva del programa, puede pensarOrd A
como un objeto que contiene lógica para comparar dosA
s. Sí, al igual queComparator
).Ahora, si
Ord A
yOrd B
, entonces su compuesto(A, B)
también debería admitir la comparación. es decirOrd (A, B)
. SiOrd A
,Ord B
yOrd C
, entoncesOrd (A, B, C)
.Podemos extender este argumento a la aridad arbitraria y decir:
Ord A, Ord B, Ord C, ..., Ord Z
⇒Ord (A, B, C, .., Z)
Llamemos a esta declaración 1.
La comparación de los compuestos funcionará tal como lo describió en su pregunta: primero se intentará la primera comparación, luego la siguiente, luego la siguiente, y así sucesivamente.
Esa es la primera parte de nuestra solución. Ahora la segunda parte.
Si sabe que
Ord A
, y sabe cómo transformarB
aA
(llamada que función de transformaciónf
), entonces también puede tenerOrd B
. ¿Cómo? Bueno, cuandoB
se comparan las dos instancias, primero las transforma paraA
usarf
y luego se aplicaOrd A
.Aquí, estamos mapeando la transformación
B → A
aOrd A → Ord B
. Esto se conoce como mapeo contravariante (ocomap
para abreviar).Ord A, (B → A)
⇒ comapOrd B
Llamemos a esta declaración 2.
Ahora apliquemos esto a su ejemplo.
Tiene un tipo de datos denominado
Person
que comprende tres campos de tipoString
.Sabemos que
Ord String
. Por la declaración 1,Ord (String, String, String)
.Podemos escribir fácilmente una función de
Person
a(String, String, String)
. (Simplemente devuelva los tres campos). Como sabemosOrd (String, String, String)
yPerson → (String, String, String)
, mediante la declaración 2, podemos usarcomap
para obtenerOrd Person
.QED
¿Cómo implemento todos estos conceptos?
La buena noticia es que no tienes que hacerlo. Ya existe una biblioteca que implementa todas las ideas descritas en esta publicación. (Si tiene curiosidad sobre cómo se implementan, puede mirar debajo del capó ).
Así es como se verá el código con él:
Explicación:
stringOrd
Es un objeto de tipoOrd<String>
. Esto corresponde a nuestra propuesta original de "comparación de apoyos".p3Ord
es un método que tomaOrd<A>
,Ord<B>
,Ord<C>
, y vuelveOrd<P3<A, B, C>>
. Esto corresponde a la declaración 1. (P3
significa producto con tres elementos. Producto es un término algebraico para compuestos).comap
corresponde a biencomap
,.F<A, B>
representa una función de transformaciónA → B
.p
es un método de fábrica para crear productos.Espero que ayude.
fuente
En lugar de métodos de comparación, es posible que desee definir varios tipos de subclases de "Comparador" dentro de la clase Persona. De esa manera, puede pasarlos a los métodos de clasificación de Colecciones estándar.
fuente
Creo que sería más confuso si su algoritmo de comparación fuera "inteligente". Me gustaría ir con los numerosos métodos de comparación que sugirió.
La única excepción para mí sería la igualdad. Para las pruebas unitarias, me ha sido útil anular los .Equals (en .net) para determinar si varios campos son iguales entre dos objetos (y no que las referencias sean iguales).
fuente
Si hay varias formas en que un usuario puede ordenar a una persona, también puede tener varias configuraciones de Comparador como constantes en alguna parte. La mayoría de las operaciones de ordenación y colecciones ordenadas toman un comparador como parámetro.
fuente
fuente
La implementación del código de la misma está aquí si tenemos que ordenar el objeto Persona en función de múltiples campos.
fuente
fuente
Si implementa el Comparable interfaz , querrá elegir una propiedad simple para ordenar. Esto se conoce como ordenamiento natural. Piense en ello como el valor predeterminado. Siempre se usa cuando no se suministra un comparador específico. Por lo general, este es el nombre, pero su caso de uso puede requerir algo diferente. Puede utilizar cualquier número de otros comparadores que pueda suministrar a varias API de colecciones para anular el orden natural.
También tenga en cuenta que, por lo general, si a.compareTo (b) == 0, entonces a.equals (b) == verdadero. Está bien si no, pero hay efectos secundarios a tener en cuenta. Vea los excelentes javadocs en la interfaz Comparable y encontrará mucha información excelente sobre esto.
fuente
Siguiente blog dado buen ejemplo comparador encadenado
http://www.codejava.net/java-core/collections/sorting-a-list-by-multiple-attributes-example
Comparador de llamadas:
fuente
A partir de la respuesta de Steve, se puede utilizar el operador ternario:
fuente
Es fácil comparar dos objetos con el método hashcode en java`
fuente
Por lo general, anulo mi
compareTo()
método de esta manera cada vez que tengo que hacer una clasificación multinivel.Aquí primero se da preferencia al nombre de la película, luego al artista y, por último, a songLength. Solo debes asegurarte de que esos multiplicadores estén lo suficientemente distantes como para no cruzar los límites del otro.
fuente
Es fácil de hacer con la biblioteca de guayaba de Google .
p.ej
Objects.equal(name, name2) && Objects.equal(age, age2) && ...
Más ejemplos:
fuente