procesamiento - stream java 8 ejemplo
Eliminar y recopilar elementos con secuencias de Java (4)
Supongamos que tengo una Collection
y un Predicate
que coincide con los elementos que me gustaría eliminar de la Collection
. Pero no solo quiero descartarlos, quiero mover los elementos combinados a una nueva colección. Haría algo como esto en Java 7:
List<E> removed = new LinkedList<>();
for (Iterator<E> i = data.iterator(); i.hasNext();) {
E e = i.next();
if (predicate.test(e)) {
removed.add(e);
i.remove();
}
}
Tengo curiosidad por saber si hay una forma de streams / Java 8 para hacerlo. Collections.removeIf()
desafortunadamente simplemente devuelve un boolean
(ni siquiera un recuento de la cantidad de elementos eliminados? Muy mal.) Imagino algo como esto (aunque por supuesto .removeAndYield(Predicate)
no existe):
List<E> removed = data.removeAndYield(predicate).collect(Collectors.toList());
Nota: esta pregunta fue inspirada por una pregunta similar ; esta pregunta es sobre el caso más general de obtener una secuencia sobre los elementos eliminados de una colección. Como se señaló en la pregunta vinculada, la solución imperativa puede ser más legible, pero tengo curiosidad si esto es posible con las transmisiones.
Editar: Claramente, podemos dividir la tarea en dos pasos separados, y asumiendo las estructuras de datos apropiadas, será eficiente. La pregunta es si esto se puede hacer en colecciones arbitrarias (que pueden no tener .contains()
eficientes).
Si desea una forma funcional de hacerlo, puede escribir su propio método.
static <E> Set<E> removeIf(Collection<? extends E> collection, Predicate<? super E> predicate) {
Set<E> removed = new HashSet<>();
for (Iterator<? extends E> i = collection.iterator(); i.hasNext();) {
E e = i.next();
if (predicate.test(e)) {
removed.add(e);
i.remove();
}
}
return removed;
}
Esto podría usarse para eliminar todos los números impares de una List
.
Set<Integer> set = new HashSet<>(Arrays.asList(1, 2, 3, 4, 5, 6));
Set<Integer> removed = removeIf(set, i -> i % 2 != 0);
System.out.println(set);
System.out.println(removed);
Si no te importa, permíteme atenuar tus requisitos un poco. :-)
Una característica del resultado deseado es que los elementos coincidentes deben terminar en una colección, y los elementos que no coinciden deben terminar en una colección diferente. En el mundo mutante anterior a Java 8, la manera más fácil de pensar en obtener una colección de elementos no coincidentes es eliminar los elementos coincidentes de la colección original.
Pero, ¿la eliminación (modificación de la lista original) es una parte intrínseca del requisito?
Si no es así, el resultado se puede lograr a través de una operación de partición simple:
Map<Boolean, List<E>> map = data.stream().collect(partitioningBy(predicate));
El mapa resultante es esencialmente dos listas, que contienen los elementos de coincidencia (clave = verdadero) y no coincidente (clave = falso).
La ventaja es que esta técnica se puede hacer de una sola vez y en paralelo si es necesario. Por supuesto, esto crea una lista duplicada de elementos no coincidentes en comparación con la eliminación de las coincidencias del original, pero este es el precio a pagar por la inmutabilidad. Las compensaciones valdrán la pena.
Solo escríbele una función reutilizable como esta:
/**
* Removes all matching element from iterator
*
* @param it
* @param predicate
*/
public static <E> void removeMatching(final Iterator<E> it, final Predicate<E> predicate) {
while (it.hasNext()) {
final E e = it.next();
if (predicate.test(e)) {
it.remove();
}
}
}
Tampoco he encontrado una solución preexistente para Streams. El uso de iterator.remove () requiere menos memoria que un conjunto temporal.
Lo mantendría simple:
Set<E> removed = set.stream()
.filter(predicate)
.collect(Collectors.toSet());
set.removeAll(removed);