Dada uma lista de elementos, desejo obter o elemento com uma determinada propriedade e removê-lo da lista. A melhor solução que encontrei é:
ProducerDTO p = producersProcedureActive
.stream()
.filter(producer -> producer.getPod().equals(pod))
.findFirst()
.get();
producersProcedureActive.remove(p);
É possível combinar get e remove em uma expressão lambda?
java
lambda
java-8
java-stream
Marco Stramezzi
fonte
fonte
get()
aqui! Você não tem ideia se está vazio ou não. Você lançará uma exceção se o elemento não estiver lá. Em vez disso, use um dos métodos seguros como ifPresent, orElse, orElseGet ou orElseThrow.list
para os quais oPredicate
é verdadeiro ou apenas o primeiro (de um possivelmente zero, um ou vários elementos)?Respostas:
Para remover o elemento da lista
por exemplo:
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);
SAÍDA: A B C
fonte
removeIf
é uma solução elegante para remover elementos de uma coleção, mas não retorna o elemento removido.Embora o segmento seja bastante antigo, ainda se pensa em fornecer solução - utilizando
Java8
.Faça uso da
removeIf
função. A complexidade do tempo éO(n)
Referência da API: removeIf docs
Suposição:
producersProcedureActive
é umList
NOTA: Com esta abordagem, você não conseguirá reter o item excluído.
fonte
Considere o uso de iteradores Java vanilla para executar a tarefa:
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; }
Vantagens :
Iterable
mesmo semstream()
suporte (pelo menos aqueles que implementamremove()
em seu iterador) .Desvantagens :
Quanto ao
outras respostas mostram claramente que é possível, mas você deve estar ciente de
ConcurrentModificationException
pode ser lançado ao remover o elemento da lista que está sendo iteradafonte
remove()
métodos que lançam UOE. (Não os das coleções JDK, é claro, mas acho injusto dizer "funciona em qualquer Iterável".)default
implementação deremoveIf
faz a mesma suposição, mas, é claro, é definida emCollection
ao invés deIterable
…A solução direta seria invocar
ifPresent(consumer)
no Opcional retornado porfindFirst()
. Este consumidor será chamado quando o opcional não estiver vazio. O benefício também é que não lançará uma exceção se a operação find retornou um opcional vazio, como seu código atual faria; em vez disso, nada acontecerá.Se você quiser retornar o valor removido, você pode
map
oOptional
ao resultado da chamadaremove
:producersProcedureActive.stream() .filter(producer -> producer.getPod().equals(pod)) .findFirst() .map(p -> { producersProcedureActive.remove(p); return p; });
Mas observe que a
remove(Object)
operação percorrerá novamente a lista para encontrar o elemento a ser removido. Se você tiver uma lista com acesso aleatório, como umArrayList
, seria melhor fazer um Stream sobre os índices da lista e encontrar o primeiro índice correspondente ao predicado:IntStream.range(0, producersProcedureActive.size()) .filter(i -> producersProcedureActive.get(i).getPod().equals(pod)) .boxed() .findFirst() .map(i -> producersProcedureActive.remove((int) i));
Com essa solução, a
remove(int)
operação atua diretamente no índice.fonte
LinkedList
talvez você não deva usar a API de fluxo, pois não há solução sem atravessar pelo menos duas vezes. Mas não conheço nenhum cenário da vida real em que a vantagem acadêmica de uma lista vinculada possa compensar sua sobrecarga real. Portanto, a solução simples é nunca usarLinkedList
.remove(Object)
apenas retorna umboolean
informando se havia um elemento para remover ou não.boxed()
você consegue umOptionalInt
que só podemap
deint
paraint
. Ao contrárioIntStream
, não hámapToObj
método. Comboxed()
, você obterá umOptional<Integer>
que permitemap
a um objeto arbitrário, ou seja, oProducerDTO
retornado porremove(int)
. O elenco deInteger
paraint
é necessário para eliminar a ambigüidade entreremove(int)
eremove(Object)
.Use pode usar o filtro do Java 8 e criar outra lista se não quiser alterar a lista antiga:
fonte
Tenho certeza de que essa será uma resposta impopular, mas 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]
conterá o elemento encontrado ou será nulo.O "truque" aqui é contornar o problema "efetivamente final" usando uma referência de array que seja efetivamente final, mas definindo seu primeiro elemento.
fonte
.orElse(null)
para obter oProducerDTO
ounull
....orElse(null)
e ter umif
, não?remove()
também usandoorElse(null)
?if(p!=null) producersProcedureActive.remove(p);
ainda é mais curto do que a expressão lambda em suaifPresent
chamada.Com Eclipse coleções você pode usar
detectIndex
junto comremove(int)
em qualquer 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);
Se você usar o
MutableList
tipo de Coleções Eclipse, poderá chamar odetectIndex
método diretamente na 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: Eu sou um committer para Eclipse Collections
fonte
Quando queremos obter vários elementos de uma lista em uma nova lista (filtrar usando um predicado) e removê-los da lista existente , não consegui encontrar uma resposta adequada em lugar nenhum.
Aqui está como podemos fazer isso usando o particionamento da 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);
Dessa forma, você remove efetivamente os elementos filtrados da lista original e os adiciona a uma nova lista.
Consulte 5.2. Collectors.partitioningPor seção deste artigo .
fonte
Como outros sugeriram, este pode ser um caso de uso para loops e iteráveis. Em minha opinião, esta é a abordagem mais simples. Se você deseja modificar a lista no local, ela não pode ser considerada uma programação funcional "real" de qualquer maneira. Mas você pode usar o
Collectors.partitioningBy()
para obter uma nova lista com elementos que satisfaçam sua condição e uma nova lista daqueles que não satisfazem. Claro, com essa abordagem, se você tiver vários elementos que satisfaçam a condição, todos eles estarão nessa lista e não apenas o primeiro.fonte
A lógica abaixo é a solução sem modificar a 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"]
fonte
Combinando minha ideia inicial e suas respostas, cheguei ao que parece ser a solução para minha própria pergunta:
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 remover o objeto usando
remove(int)
que não percorre novamente a lista (como sugerido por @Tunaki) e permite retornar o objeto removido ao chamador da função.Li suas respostas que me sugerem escolher métodos seguros como em
ifPresent
vez de,get
mas não encontro uma maneira de usá-los neste cenário.Existe alguma desvantagem importante neste tipo de solução?
Edite os seguintes conselhos de @Holger
Esta deve ser a função que eu precisava
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; }); }
fonte
get
e capturar a exceção. Isso não é apenas um estilo ruim, mas também pode causar um desempenho ruim. A solução limpa é ainda mais simples,return /* stream operation*/.findFirst() .map(i -> producersProcedureActive.remove((int)i)) .orElseGet(() -> { logger.error("No producer found with POD [" + pod + "]"); return null; });
a tarefa é: obter ✶ e ✶ remover o elemento da 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 } ) );
fonte