suma - Java 8 lambda obtener y eliminar elemento de la lista
streams lambda java (12)
Dada una lista de elementos, quiero obtener el elemento con una propiedad dada y eliminarlo de la lista. La mejor solución que encontré es:
ProducerDTO p = producersProcedureActive
.stream()
.filter(producer -> producer.getPod().equals(pod))
.findFirst()
.get();
producersProcedureActive.remove(p);
¿Es posible combinar get y remove en una expresión lambda?
Aunque el subproceso es bastante antiguo, se pensó que proporcionaría una solución, utilizando
Java8
.
Haga uso de la función
removeIf
.
La complejidad del tiempo es
O(n)
producersProcedureActive.removeIf(producer -> producer.getPod().equals(pod));
Referencia de API: removeIf docs
Supuesto:
producersProcedureActive
es una
List
NOTA: con este enfoque, no podrá obtener el elemento eliminado.
Combinando mi idea inicial y sus respuestas, llegué a lo que parece ser la solución a mi propia pregunta:
public ProducerDTO findAndRemove(String pod) {
ProducerDTO p = null;
try {
p = IntStream.range(0, producersProcedureActive.size())
.filter(i -> producersProcedureActive.get(i).getPod().equals(pod))
.boxed()
.findFirst()
.map(i -> producersProcedureActive.remove((int)i))
.get();
logger.debug(p);
} catch (NoSuchElementException e) {
logger.error("No producer found with POD [" + pod + "]");
}
return p;
}
Permite eliminar el objeto utilizando
remove(int)
que no recorre nuevamente la lista (como lo sugiere @Tunaki)
y
permite devolver el objeto eliminado a la función de llamada.
Leí sus respuestas que me sugieren elegir métodos seguros como
ifPresent
lugar de
get
pero no encuentro la manera de usarlos en este escenario.
¿Hay algún inconveniente importante en este tipo de solución?
Editar siguiendo los consejos de @Holger
Esta debería ser la función que necesitaba
public ProducerDTO findAndRemove(String pod) {
return IntStream.range(0, producersProcedureActive.size())
.filter(i -> producersProcedureActive.get(i).getPod().equals(pod))
.boxed()
.findFirst()
.map(i -> producersProcedureActive.remove((int)i))
.orElseGet(() -> {
logger.error("No producer found with POD [" + pod + "]");
return null;
});
}
Como otros han sugerido, este podría ser un caso de uso para bucles e iterables.
En mi opinión, este es el enfoque más simple.
Si desea modificar la lista in situ, no puede considerarse programación funcional "real" de todos modos.
Pero podría usar
Collectors.partitioningBy()
para obtener una nueva lista con elementos que satisfagan su condición, y una nueva lista de los que no.
Por supuesto, con este enfoque, si tiene múltiples elementos que satisfacen la condición, todos estarán en esa lista y no solo el primero.
Con
Eclipse Collections
puede usar
detectIndex
junto con
remove(int)
en cualquier java.util.List.
List<Integer> integers = Lists.mutable.with(1, 2, 3, 4, 5);
int index = Iterate.detectIndex(integers, i -> i > 2);
if (index > -1) {
integers.remove(index);
}
Assert.assertEquals(Lists.mutable.with(1, 2, 4, 5), integers);
Si usa el tipo
MutableList
de Eclipse Collections, puede llamar al método
detectIndex
directamente en la lista.
MutableList<Integer> integers = Lists.mutable.with(1, 2, 3, 4, 5);
int index = integers.detectIndex(i -> i > 2);
if (index > -1) {
integers.remove(index);
}
Assert.assertEquals(Lists.mutable.with(1, 2, 4, 5), integers);
Nota: Soy un committer para Eclipse Collections
Considere usar iteradores vanilla java para realizar la tarea:
public static <T> T findAndRemoveFirst(Iterable<? extends T> collection, Predicate<? super T> test) {
T value = null;
for (Iterator<? extends T> it = collection.iterator(); it.hasNext();)
if (test.test(value = it.next())) {
it.remove();
return value;
}
return null;
}
Ventajas :
- Es claro y obvio.
- Atraviesa solo una vez y solo hasta el elemento correspondiente.
-
Puede hacerlo en cualquier
Iterable
incluso sin el soporte destream()
(al menos aquellos que implementanremove()
en su iterador) .
Desventajas
- No puede hacerlo en su lugar como una sola expresión (se requiere método auxiliar o variable)
En cuanto a
¿Es posible combinar get y remove en una expresión lambda?
otras respuestas muestran claramente que es posible, pero debe tener en cuenta
- La búsqueda y eliminación pueden atravesar la lista dos veces
-
ConcurrentModificationException
puedeConcurrentModificationException
al eliminar un elemento de la lista que se está iterando
Cuando queremos obtener múltiples elementos de una Lista en una nueva lista (filtrar usando un predicado) y eliminarlos de la lista existente , no pude encontrar una respuesta adecuada en ningún lado.
Aquí es cómo podemos hacerlo utilizando la partición de API Java Streaming.
Map<Boolean, List<ProducerDTO>> classifiedElements = producersProcedureActive
.stream()
.collect(Collectors.partitioningBy(producer -> producer.getPod().equals(pod)));
// get two new lists
List<ProducerDTO> matching = classifiedElements.get(true);
List<ProducerDTO> nonMatching = classifiedElements.get(false);
// OR get non-matching elements to the existing list
producersProcedureActive = classifiedElements.get(false);
De esta manera, elimina efectivamente los elementos filtrados de la lista original y los agrega a una nueva lista.
Consulte el 5.2. Colectores.particiónPor sección de este artículo .
Estoy seguro de que esta será una respuesta impopular, pero funciona ...
ProducerDTO[] p = new ProducerDTO[1];
producersProcedureActive
.stream()
.filter(producer -> producer.getPod().equals(pod))
.findFirst()
.ifPresent(producer -> {producersProcedureActive.remove(producer); p[0] = producer;}
p[0]
mantendrá el elemento encontrado o será nulo.
El "truco" aquí es eludir el problema "efectivamente final" mediante el uso de una referencia de matriz que es efectivamente final, pero estableciendo su primer elemento.
La siguiente lógica es la solución sin modificar la lista original.
List<String> str1 = new ArrayList<String>();
str1.add("A");
str1.add("B");
str1.add("C");
str1.add("D");
List<String> str2 = new ArrayList<String>();
str2.add("D");
str2.add("E");
List<String> str3 = str1.stream()
.filter(item -> !str2.contains(item))
.collect(Collectors.toList());
str1 // ["A", "B", "C", "D"]
str2 // ["D", "E"]
str3 // ["A", "B", "C"]
La solución directa sería invocar
ifPresent(consumer)
en el Opcional devuelto por
findFirst()
.
Este consumidor será invocado cuando lo opcional no esté vacío.
El beneficio también es que no arrojará una excepción si la operación de búsqueda devuelve un opcional vacío, como lo haría su código actual;
en cambio, no pasará nada.
Si desea devolver el valor eliminado, puede
map
el
Optional
al resultado de llamar a
remove
:
producersProcedureActive.stream()
.filter(producer -> producer.getPod().equals(pod))
.findFirst()
.map(p -> {
producersProcedureActive.remove(p);
return p;
});
Pero tenga en cuenta que la operación
remove(Object)
volverá a recorrer la lista para encontrar el elemento a eliminar.
Si tiene una lista con acceso aleatorio, como una
ArrayList
, sería mejor hacer un Stream sobre los índices de la lista y encontrar el primer índice que coincida con el predicado:
IntStream.range(0, producersProcedureActive.size())
.filter(i -> producersProcedureActive.get(i).getPod().equals(pod))
.boxed()
.findFirst()
.map(i -> producersProcedureActive.remove((int) i));
Con esta solución, la operación
remove(int)
opera directamente en el índice.
Use puede usar el filtro de Java 8 y crear otra lista si no desea cambiar la lista anterior:
List<ProducerDTO> result = producersProcedureActive
.stream()
.filter(producer -> producer.getPod().equals(pod))
.collect(Collectors.toList());
la tarea es: obtener ✶ y ✶ eliminar elemento de la lista
p.stream().collect( Collectors.collectingAndThen( Collector.of(
ArrayDeque::new,
(a, producer) -> {
if( producer.getPod().equals( pod ) )
a.addLast( producer );
},
(a1, a2) -> {
return( a1 );
},
rslt -> rslt.pollFirst()
),
(e) -> {
if( e != null )
p.remove( e ); // remove
return( e ); // get
} ) );
Eliminar elemento de la lista
objectA.removeIf (x -> condiciones);
por ejemplo: objectA.removeIf (x -> blockWorkerIds.contains (x));
List<String> str1 = new ArrayList<String>();
str1.add("A");
str1.add("B");
str1.add("C");
str1.add("D");
List<String> str2 = new ArrayList<String>();
str2.add("D");
str2.add("E");
str1.removeIf(x -> str2.contains(x));
str1.forEach(System.out::println);
SALIDA: A B C