Dada una lista de elementos, quiero obtener el elemento con una propiedad determinada 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?
java
lambda
java-8
java-stream
Marco Stramezzi
fuente
fuente
get()
aquí! No tienes idea de si está vacío o no. Lanzará una excepción si el elemento no estaba allí. En su lugar, utilice uno de los métodos seguros como ifPresent, orElse, orElseGet, oElseThrow.list
que elPredicate
es verdadero o solo el primero (de un posiblemente cero, uno o muchos elementos)?Respuestas:
Para eliminar elemento de la lista
p.ej:
objectA.removeIf(x -> blockedWorkerIds.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
fuente
removeIf
es una solución elegante para eliminar elementos de una colección, pero no devuelve el elemento eliminado.Aunque el hilo es bastante antiguo, todavía se cree que proporciona una solución: el uso
Java8
.Haz uso de la
removeIf
función. La complejidad del tiempo esO(n)
Referencia de API: removeIf docs
Supuesto:
producersProcedureActive
es unList
NOTA: Con este enfoque, no podrá retener el elemento eliminado.
fuente
Considere usar iteradores de Java vainilla 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 :
Iterable
incluso sinstream()
soporte (al menos aquellos que implementanremove()
en su iterador) .Desventajas :
En cuanto a
otras respuestas muestran claramente que es posible, pero debe tener en cuenta
ConcurrentModificationException
se puede lanzar al eliminar un elemento de la lista que se está iterandofuente
remove()
métodos que arrojan UOE. (No los de las colecciones de JDK, por supuesto, pero creo que es injusto decir "funciona en cualquier Iterable".)default
implementación deremoveIf
hace la misma suposición, pero, por supuesto, se define enCollection
lugar deIterable
…La solución directa sería invocar
ifPresent(consumer)
el Opcional devuelto porfindFirst()
. Este consumidor se invocará cuando el opcional no esté vacío. El beneficio también es que no lanzará 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 eliminadas, podrá
map
elOptional
que el resultado de la llamadaremove
:producersProcedureActive.stream() .filter(producer -> producer.getPod().equals(pod)) .findFirst() .map(p -> { producersProcedureActive.remove(p); return p; });
Pero tenga en cuenta que la
remove(Object)
operación volverá a recorrer la lista para encontrar el elemento a eliminar. Si tiene una lista con acceso aleatorio, como unaArrayList
, sería mejor hacer una secuencia 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
remove(int)
operación opera directamente sobre el índice.fuente
LinkedList
, quizás no debería usar la API de transmisión, ya que no hay solución sin atravesar al menos dos veces. Pero no conozco ningún escenario de la vida real en el que la ventaja académica de una lista vinculada pueda compensar su sobrecarga real. Entonces, la solución simple es no usar nuncaLinkedList
.remove(Object)
solo devuelve unboolean
indicador de si había un elemento para eliminar o no.boxed()
se obtiene unOptionalInt
que sólo puedemap
desdeint
hastaint
. A diferenciaIntStream
, no existe unmapToObj
método. Conboxed()
, obtendrá unOptional<Integer>
que permitemap
a un objeto arbitrario, es decir, elProducerDTO
devuelto porremove(int)
. El elenco deInteger
aint
es necesario para desambiguar entreremove(int)
yremove(Object)
.Puede usar el filtro de Java 8 y crear otra lista si no desea cambiar la lista anterior:
fuente
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]
contendrá el elemento encontrado o será nulo.El "truco" aquí es eludir el problema "efectivamente final" utilizando una referencia de matriz que sea efectivamente final, pero estableciendo su primer elemento.
fuente
.orElse(null)
para obtener elProducerDTO
onull
….orElse(null)
y tener unif
, ¿no?remove()
también usandoorElse(null)
?if(p!=null) producersProcedureActive.remove(p);
eso es aún más corto que la expresión lambda en suifPresent
llamada.Con las colecciones de Eclipse se puede utilizar
detectIndex
junto conremove(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
MutableList
tipo de Eclipse Collections, puede llamar aldetectIndex
método 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 comprometido con las colecciones de Eclipse
fuente
Cuando queremos obtener varios 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 ninguna parte.
Así es como podemos hacerlo usando el particionamiento de la API de 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 de manera efectiva los elementos filtrados de la lista original y los agrega a una nueva lista.
Consulte el 5.2. Collectors.partitioningBy sección de este artículo .
fuente
Como han sugerido otros, 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 en el lugar, no puede considerarse programación funcional "real" de todos modos. Pero podría usar
Collectors.partitioningBy()
para obtener una nueva lista con elementos que satisfacen su condición, y una nueva lista de aquellos que no. Por supuesto, con este enfoque, si tiene varios elementos que satisfacen la condición, todos estarán en esa lista y no solo el primero.fuente
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"]
fuente
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 usando
remove(int)
que no recorra nuevamente la lista (como lo sugiere @Tunaki) y permite devolver el objeto eliminado al llamador de la función.Leí sus respuestas que me sugieren que elija métodos seguros como en
ifPresent
lugar deget
pero no encuentro la manera de usarlos en este escenario.¿Existe algún inconveniente importante en este tipo de solución?
Edite 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; }); }
fuente
get
y capturar la excepción. Eso no solo es un mal estilo, sino que también puede causar un mal rendimiento. La solución limpia es aún más simple,return /* stream operation*/.findFirst() .map(i -> producersProcedureActive.remove((int)i)) .orElseGet(() -> { logger.error("No producer found with POD [" + pod + "]"); return null; });
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 } ) );
fuente