No Java 8, como filtrar uma coleção usando o método Stream
API, verificando a distinção de uma propriedade de cada objeto?
Por exemplo, tenho uma lista de Person
objetos e desejo remover pessoas com o mesmo nome,
persons.stream().distinct();
Usarei a verificação de igualdade padrão para um Person
objeto, então preciso de algo como,
persons.stream().distinct(p -> p.getName());
Infelizmente, o distinct()
método não possui essa sobrecarga. Sem modificar a verificação de igualdade dentro da Person
classe, é possível fazer isso de forma sucinta?
fonte
Function<? super T, ?>
, nãoFunction<? super T, Object>
. Também deve-se notar que, para o fluxo paralelo ordenado, esta solução não garante qual objeto será extraído (diferente do normaldistinct()
). Também para fluxos sequenciais, há uma sobrecarga adicional no uso do CHM (que está ausente na solução @nosid). Finalmente, esta solução viola o contrato dofilter
método cujo predicado deve ser sem estado, conforme declarado no JavaDoc. Mesmo assim votado.distinctByKey
não tem idéia de se está sendo usada em um fluxo paralelo. Ele usa o CHM no caso de estar sendo usado em paralelo, embora isso adicione sobrecarga no caso seqüencial, como Tagir Valeev observou acima.distinctByKey
. Mas funciona se você ligardistinctByKey
sempre, para criar uma instância nova do Predicado toda vez..filter(distinctByKey(...))
. Ele executará o método uma vez e retornará o predicado. Então, basicamente, o mapa já está sendo reutilizado se você o usar corretamente dentro de um fluxo. Se você tornar o mapa estático, ele será compartilhado para todos os usos. Portanto, se você tiver dois fluxos usando issodistinctByKey()
, ambos usariam o mesmo mapa, que não é o que você deseja.CallSite
será vinculado aoget$Lambda
método - que retornará uma nova instânciaPredicate
o tempo todo, mas essas instâncias compartilharão o mesmomap
efunction
até onde eu entendo. Muito agradável!Uma alternativa seria colocar as pessoas em um mapa usando o nome como chave:
Observe que a Pessoa mantida, no caso de um nome duplicado, será o primeiro a ser convocado.
fonte
distinct()
sem essa sobrecarga? Como alguma implementação saberia se já viu um objeto antes sem lembrar de todos os valores distintos que viu? Portanto, a sobrecargatoMap
edistinct
provavelmente é a mesma.distinct()
si cria.persons.collect(toMap(Person::getName, p -> p, (p, q) -> p, LinkedHashMap::new)).values();
TreeSet
) que já é distinta de qualquer maneira ousorted
no fluxo que também armazena em buffer todos os elementos.Você pode agrupar os objetos da pessoa em outra classe, que compara apenas os nomes das pessoas. Depois, você desembrulha os objetos quebrados para que uma pessoa seja transmitida novamente. As operações de fluxo podem ter a seguinte aparência:
A classe
Wrapper
pode ter a seguinte aparência:fonte
equals
método pode ser simplificado parareturn other instanceof Wrapper && ((Wrapper) other).person.getName().equals(person.getName());
Outra solução, usando
Set
. Pode não ser a solução ideal, mas funcionaOu, se você pode modificar a lista original, pode usar o método removeIf
fonte
Há uma abordagem mais simples usando um TreeSet com um comparador personalizado.
fonte
Também podemos usar o RxJava ( biblioteca de extensão reativa muito poderosa )
ou
fonte
Observable
é baseado em push, ao passo queStream
é baseado em pull. stackoverflow.com/questions/30216979/…Flux.fromIterable(persons).distinct(p -> p.getName())
Stream
API", não "não necessariamente usando fluxo". Dito isto, esta é uma ótima solução para o problema XY de filtrar o fluxo para valores distintos.Você pode usar o
groupingBy
coletor:Se você quiser ter outro fluxo, use:
fonte
Você pode usar o
distinct(HashingStrategy)
método nas Coleções Eclipse .Se você puder refatorar
persons
para implementar uma interface de Coleções do Eclipse, poderá chamar o método diretamente na lista.HashingStrategy é simplesmente uma interface de estratégia que permite definir implementações personalizadas de iguais e hashcode.
Nota: Sou um colaborador das Coleções Eclipse.
fonte
Eu recomendo usar o Vavr , se você puder. Com esta biblioteca, você pode fazer o seguinte:
fonte
Você pode usar a biblioteca StreamEx :
fonte
String
s graças à internação de strings, mas também não.Estendendo a resposta de Stuart Marks, isso pode ser feito de maneira mais curta e sem um mapa simultâneo (se você não precisar de fluxos paralelos):
Então ligue:
fonte
Collections.synchronizedSet(new HashSet<>())
. Mas provavelmente seria mais lento do que com aConcurrentHashMap
.Abordagem semelhante que Saeed Zarinfam usou, mas com mais estilo Java 8 :)
fonte
flatMap(plans -> plans.stream().findFirst().stream())
isso evita o uso de get em opcional Opcional #Eu fiz uma versão genérica:
Um exemplo:
fonte
Outra biblioteca que suporta isso é o jOOλ , e seu
Seq.distinct(Function<T,U>)
método:Sob o capô , ele faz praticamente a mesma coisa que a resposta aceita .
fonte
fonte
Minha abordagem é agrupar todos os objetos com a mesma propriedade, reduzir os grupos para o tamanho de 1 e, finalmente, coletá-los como a
List
.fonte
A lista de objetos distintos pode ser encontrada usando:
fonte
A maneira mais fácil de implementar isso é pular o recurso de classificação, pois ele já fornece um opcional
Comparator
que pode ser criado usando a propriedade de um elemento. Então você precisa filtrar as duplicatas, o que pode ser feito usando um statefullPredicate
que usa o fato de que, para um fluxo classificado, todos os elementos iguais são adjacentes:Obviamente, um statefull
Predicate
não é seguro para threads, no entanto, se essa for sua necessidade, você pode mover essa lógica para aeCollector
deixar o fluxo cuidar da segurança de threads ao usar o seuCollector
. Isso depende do que você deseja fazer com o fluxo de elementos distintos que você não nos contou na sua pergunta.fonte
Com base na resposta de @ josketres, criei um método de utilitário genérico:
Você pode tornar isso mais amigável ao Java 8 criando um coletor .
fonte
Talvez seja útil para alguém. Eu tinha um pouco mais um requisito. Ter uma lista de objetos
A
de terceiros remove todos os que têm o mesmoA.b
campo para o mesmoA.id
(váriosA
objetos com o mesmoA.id
na lista). A resposta da partição de fluxo por Tagir Valeev me inspirou a usar osCollector
retornos personalizadosMap<A.id, List<A>>
. SimplesflatMap
fará o resto.fonte
Eu tive uma situação, na qual eu deveria obter elementos distintos da lista com base em 2 chaves. Se você deseja distintas com base em duas chaves ou pode compor uma chave, tente este
fonte
No meu caso, eu precisava controlar qual era o elemento anterior. Em seguida, criei um Predicado com estado, onde eu controlava se o elemento anterior era diferente do elemento atual; nesse caso, eu o mantinha.
fonte
Minha solução nesta listagem:
Na minha situação, quero encontrar valores distintos e colocá-los na lista.
fonte
Enquanto a resposta mais alta votada é absolutamente a melhor resposta em Java 8, é ao mesmo tempo absolutamente pior em termos de desempenho. Se você realmente deseja um aplicativo de baixo desempenho e ruim, vá em frente e use-o. O simples requisito de extrair um conjunto exclusivo de Nomes de Pessoas deve ser alcançado com mero "For-Each" e um "Set". As coisas pioram ainda mais se a lista estiver acima do tamanho 10.
Considere que você tem uma coleção de 20 objetos, assim:
Onde seu objeto se
SimpleEvent
parece com isso:E para testar, você tem um código JMH como este (observe que estou usando o mesmo Predicado distintoByKey mencionado na resposta aceita):
Então você terá resultados de benchmark como este:
E, como você pode ver, um For-Each simples é 3 vezes melhor em taxa de transferência e menos em pontuação de erro em comparação com o Java 8 Stream.
Maior rendimento, melhor desempenho
fonte
fonte
Se você deseja seguir a lista de pessoas, seria a maneira mais simples
Além disso, se você deseja encontrar uma lista distinta ou exclusiva de nomes , não Pessoa , você também pode usar os dois métodos a seguir.
Método 1: usando
distinct
Método 2: usando
HashSet
fonte
Person
s.O código mais simples que você pode escrever:
fonte