En Java 8, ¿cómo puedo filtrar una colección usando la Stream
API comprobando la distinción de una propiedad de cada objeto?
Por ejemplo, tengo una lista de Person
objetos y quiero eliminar personas con el mismo nombre,
persons.stream().distinct();
Usaré la verificación de igualdad predeterminada para un Person
objeto, así que necesito algo como,
persons.stream().distinct(p -> p.getName());
Lamentablemente, el distinct()
método no tiene tal sobrecarga. Sin modificar la verificación de igualdad dentro de la Person
clase, ¿es posible hacer esto sucintamente?
fuente
Function<? super T, ?>
, noFunction<? super T, Object>
. También debe tenerse en cuenta que para la secuencia paralela ordenada, esta solución no garantiza qué objeto se extraerá (a diferencia de lo normaldistinct()
). También para las secuencias secuenciales hay una sobrecarga adicional en el uso de CHM (que está ausente en la solución @nosid). Finalmente, esta solución viola el contrato delfilter
método cuyo predicado debe ser sin estado como se indica en JavaDoc. Sin embargo, votó positivamente.distinctByKey
no tiene idea de si se está utilizando dentro de una secuencia paralela. Utiliza CHM en caso de que se use en paralelo, aunque esto agrega una sobrecarga en el caso secuencial como Tagir Valeev señaló anteriormente.distinctByKey
. Pero funciona si llamadistinctByKey
cada vez, de modo que crea una nueva instancia de Predicate cada vez..filter(distinctByKey(...))
. Ejecutará el método una vez y devolverá el predicado. Básicamente, el mapa ya se está reutilizando si lo usa correctamente dentro de una secuencia. Si hiciera el mapa estático, el mapa se compartiría para todos los usos. Entonces, si tiene dos transmisiones que usan estodistinctByKey()
, ambas usarían el mismo mapa, que no es lo que desea.CallSite
estará vinculado alget$Lambda
método, que devolverá una nueva instancia dePredicate
todo el tiempo, pero esas instancias compartirán lo mismomap
yfunction
hasta donde yo entiendo. ¡Muy agradable!Una alternativa sería colocar a las personas en un mapa usando el nombre como clave:
Tenga en cuenta que la Persona que se mantiene, en caso de un nombre duplicado, será la primera encontrada.
fuente
distinct()
sin esa sobrecarga? ¿Cómo sabría cualquier implementación si ha visto un objeto antes sin recordar todos los valores distintos que ha visto? Por lo tanto, la sobrecarga detoMap
ydistinct
es muy probable que sea la misma.distinct()
crea.persons.collect(toMap(Person::getName, p -> p, (p, q) -> p, LinkedHashMap::new)).values();
TreeSet
) que ya es distinta de todos modos osorted
en la secuencia que también almacena todos los elementos.Puede ajustar los objetos de persona en otra clase, que solo compara los nombres de las personas. Luego, desenvuelve los objetos envueltos para que una persona vuelva a aparecer. Las operaciones de transmisión pueden tener el siguiente aspecto:
La clase
Wrapper
podría verse de la siguiente manera:fuente
equals
método se puede simplificar areturn other instanceof Wrapper && ((Wrapper) other).person.getName().equals(person.getName());
Otra solución, usando
Set
. Puede que no sea la solución ideal, pero funcionaO si puede modificar la lista original, puede usar el método removeIf
fuente
Hay un enfoque más simple usando un TreeSet con un comparador personalizado.
fuente
También podemos usar RxJava ( biblioteca de extensión reactiva muy potente )
o
fuente
Observable
está basado en push mientras queStream
está basado en pull. stackoverflow.com/questions/30216979/…Flux.fromIterable(persons).distinct(p -> p.getName())
Stream
API", no "no necesariamente usando stream". Dicho esto, esta es una gran solución al problema XY de filtrar el flujo a valores distintos.Puedes usar el
groupingBy
colector:Si desea tener otra transmisión, puede usar esto:
fuente
Puede usar el
distinct(HashingStrategy)
método en Eclipse Collections .Si puede refactorizar
persons
para implementar una interfaz Eclipse Collections, puede llamar al método directamente en la lista.HashingStrategy es simplemente una interfaz de estrategia que le permite definir implementaciones personalizadas de equals y hashcode.
Nota: Soy un committer para Eclipse Collections.
fuente
Recomiendo usar Vavr , si puedes. Con esta biblioteca puedes hacer lo siguiente:
fuente
Puede usar la biblioteca StreamEx :
fuente
String
s gracias al interning de cadenas, pero también puede no funcionar.Ampliando la respuesta de Stuart Marks, esto se puede hacer de una manera más corta y sin un mapa concurrente (si no necesita flujos paralelos):
Luego llame:
fuente
Collections.synchronizedSet(new HashSet<>())
lugar. Pero probablemente sería más lento que con unConcurrentHashMap
.Enfoque similar que utilizó Saeed Zarinfam pero más estilo Java 8 :)
fuente
flatMap(plans -> plans.stream().findFirst().stream())
él, evita el uso de get on OpcionalHice una versión genérica:
Un ejemplo:
fuente
Otra biblioteca que admite esto es jOOλ , y su
Seq.distinct(Function<T,U>)
método:Sin embargo, bajo el capó , hace prácticamente lo mismo que la respuesta aceptada .
fuente
fuente
Mi enfoque para esto es agrupar todos los objetos con la misma propiedad, luego acortar los grupos al tamaño de 1 y luego finalmente reunirlos como a
List
.fuente
La lista de objetos distintos se puede encontrar usando:
fuente
La forma más fácil de implementar esto es saltar a la función de clasificación, ya que proporciona una opción
Comparator
que se puede crear utilizando la propiedad de un elemento. Luego, debe filtrar los duplicados que se pueden hacer usando un estado completoPredicate
que utiliza el hecho de que para una secuencia ordenada todos los elementos iguales son adyacentes:Por supuesto, un estado completo
Predicate
no es seguro para subprocesos, sin embargo, si esa es su necesidad, puede mover esta lógica aCollector
y dejar que la transmisión se encargue de la seguridad de subprocesos cuando use suCollector
. Esto depende de lo que quiera hacer con la secuencia de elementos distintos que no nos dijo en su pregunta.fuente
Sobre la base de la respuesta de @ josketres, creé un método de utilidad genérico:
Puede hacer que esto sea más compatible con Java 8 creando un recopilador .
fuente
Quizás sea útil para alguien. Tenía un poco otro requisito. Tener una lista de objetos
A
de terceros elimina todos los que tienen el mismoA.b
campo para el mismoA.id
(A
objeto múltiple con el mismoA.id
en la lista). La respuesta de partición de flujo de Tagir Valeev me inspiró a usar la costumbreCollector
que regresaMap<A.id, List<A>>
. SimpleflatMap
hará el resto.fuente
Tuve una situación en la que se suponía que debía obtener elementos distintos de la lista basados en 2 claves. Si desea una distinción basada en dos claves o una clave compuesta, intente esto
fuente
En mi caso, necesitaba controlar cuál era el elemento anterior. Luego creé un predicado con estado donde controlaba si el elemento anterior era diferente del elemento actual, en ese caso lo guardé.
fuente
Mi solución en este listado:
En mi situación, quiero encontrar valores distintos y ponerlos en la lista.
fuente
Si bien la respuesta más votada es absolutamente la mejor respuesta para Java 8, al mismo tiempo es absolutamente peor en términos de rendimiento. Si realmente desea una aplicación de bajo rendimiento, siga adelante y úsela. El simple requisito de extraer un conjunto único de nombres de personas se logrará mediante un mero "para cada uno" y un "conjunto". Las cosas empeoran aún más si la lista supera el tamaño de 10.
Considere que tiene una colección de 20 objetos, como este:
Donde objetas se
SimpleEvent
ve así:Y para probar, tiene un código JMH como este (tenga en cuenta que estoy usando el mismo distintivo distinto de KeyKey mencionado en la respuesta aceptada):
Entonces tendrás resultados de referencia como este:
Y como puede ver, un For-Each simple es 3 veces mejor en rendimiento y menos en puntaje de error en comparación con Java 8 Stream.
Mayor rendimiento, mejor rendimiento
fuente
fuente
Si desea hacer una lista de personas, la siguiente sería
Además, si desea encontrar una lista de nombres distinta o única , no Persona , también puede hacerlo utilizando los dos métodos siguientes.
Método 1: usando
distinct
Método 2: usando
HashSet
fuente
Person
s.El código más simple que puedes escribir:
fuente