Estou tentando usar o Java 8 Stream
s para encontrar elementos em um LinkedList
. Quero garantir, no entanto, que haja uma e apenas uma correspondência com os critérios de filtro.
Pegue este código:
public static void main(String[] args) {
LinkedList<User> users = new LinkedList<>();
users.add(new User(1, "User1"));
users.add(new User(2, "User2"));
users.add(new User(3, "User3"));
User match = users.stream().filter((user) -> user.getId() == 1).findAny().get();
System.out.println(match.toString());
}
static class User {
@Override
public String toString() {
return id + " - " + username;
}
int id;
String username;
public User() {
}
public User(int id, String username) {
this.id = id;
this.username = username;
}
public void setUsername(String username) {
this.username = username;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public int getId() {
return id;
}
}
Este código encontra um com User
base em seu ID. Mas não há garantias de quantos User
s correspondem ao filtro.
Alterando a linha de filtro para:
User match = users.stream().filter((user) -> user.getId() < 0).findAny().get();
Vai jogar um NoSuchElementException
(bom!)
Eu gostaria que isso gerasse um erro se houver várias correspondências. Existe uma maneira de fazer isso?
java
lambda
java-8
java-stream
Ryvantage
fonte
fonte
count()
é uma operação do terminal, então você não pode fazer isso. O fluxo não pode ser usado depois.Stream::size
?Stream
s muito mais do que eu fiz antes ...LinkedHashSet
(assumindo que deseja preservar o pedido de inserção) ou um o tempoHashSet
todo. Se sua coleção é usada apenas para encontrar um único ID de usuário, por que você está coletando todos os outros itens? Se houver um potencial em que você sempre precisará encontrar algum ID de usuário que também precise ser exclusivo, por que usar uma lista e não um conjunto? Você está programando para trás. Use a coleção certa para o trabalho e salvar a si mesmo essa dor de cabeçaRespostas:
Crie um costume
Collector
Usamos
Collectors.collectingAndThen
para construir o nosso desejadoCollector
porList
com oCollectors.toList()
coletor.IllegalStateException
iflist.size != 1
.Usado como:
Você pode personalizar isso o
Collector
quanto quiser, por exemplo, dar a exceção como argumento no construtor, ajustá-lo para permitir dois valores e mais.Uma solução alternativa - sem dúvida menos elegante -:
Você pode usar uma 'solução alternativa' que envolva
peek()
e umaAtomicInteger
, mas realmente não deveria estar usando isso.O que você poderia fazer com istead é apenas coletá-lo em um
List
, assim:fonte
Iterables.getOnlyElement
encurtariam essas soluções e forneceriam melhores mensagens de erro. Apenas uma dica para colegas leitores que já usam o Google Guava.singletonCollector()
definição obsoleta pela versão que permanece na postagem e renomeando-a paratoSingleton()
. Minha experiência em fluxo Java está um pouco enferrujada, mas a renomeação parece útil para mim. Analisar essa alteração levou 2 minutos, no máximo. Se você não tiver tempo para revisar edições, posso sugerir que você peça a alguém para fazer isso no futuro, talvez na sala de bate-papo Java ?Por uma questão de exaustividade, eis o 'one-liner' correspondente à excelente resposta do @ prunge:
Isso obtém o único elemento correspondente do fluxo, lançando
NoSuchElementException
caso o fluxo esteja vazio ouIllegalStateException
caso o fluxo contenha mais de um elemento correspondente.Uma variação dessa abordagem evita lançar uma exceção antecipadamente e, em vez disso, representa o resultado como
Optional
contendo o elemento único ou nada (vazio) se houver zero ou vários elementos:fonte
get()
paraorElseThrow()
As outras respostas que envolvem a escrita de um costume
Collector
são provavelmente mais eficientes (como o de Louis Wasserman , +1), mas se você quiser concisão, sugiro o seguinte:Em seguida, verifique o tamanho da lista de resultados.
fonte
limit(2)
desta solução? Que diferença faria se a lista resultante fosse 2 ou 100? Se for maior que 1.Collectors.collectingAndThen(toList(), l -> { if (l.size() == 1) return l.get(0); throw new RuntimeException(); })
maxSize: the number of elements the stream should be limited to
. Então, não deveria ser em.limit(1)
vez de.limit(2)
?result.size()
para garantir que seja igual a 1. Se for 2, haverá mais de uma correspondência, portanto, é um erro. Se o código o fizesselimit(1)
, mais de uma correspondência resultaria em um único elemento, que não pode ser diferenciado de haver exatamente uma correspondência. Isso perderia um caso de erro com o qual o OP estava preocupado.A goiaba fornece o
MoreCollectors.onlyElement()
que faz a coisa certa aqui. Mas se você tiver que fazer isso sozinho, poderá fazer o seu próprioCollector
para isso:... ou usando seu próprio
Holder
tipo em vez deAtomicReference
. Você pode reutilizar isso oCollector
quanto quiser.fonte
Collector
era o caminho a seguir.List
é mais caro que uma única referência mutável.MoreCollectors.onlyElement()
deve realmente ser a primeira (e talvez a única :))Use goiabas
MoreCollectors.onlyElement()
( JavaDoc ).Ele faz o que você deseja e lança um
IllegalArgumentException
se o fluxo consistir em dois ou mais elementos e umNoSuchElementException
se o fluxo estiver vazio.Uso:
fonte
MoreCollectors
faz parte do ainda inéditos (em 2016-12) unreleased versão 21.A operação "escape hatch" que permite fazer coisas estranhas que não são suportadas por fluxos é solicitar um
Iterator
:A goiaba possui um método de conveniência para
Iterator
obter e obter o único elemento, lançando se houver zero ou vários elementos, o que poderia substituir as linhas n-1 inferiores aqui.fonte
Atualizar
Boa sugestão no comentário de @Holger:
Resposta original
A exceção é lançada
Optional#get
, mas se você tiver mais de um elemento que não ajudará. Você pode coletar os usuários em uma coleção que aceita apenas um item, por exemplo:que lança um
java.lang.IllegalStateException: Queue full
, mas parece muito hacky.Ou você pode usar uma redução combinada com uma opcional:
A redução retorna essencialmente:
O resultado é então envolvido em um opcional.
Mas a solução mais simples provavelmente seria coletar apenas uma coleção, verificar se o tamanho é 1 e obter o único elemento.
fonte
null
) para impedir o usoget()
. Infelizmente, o seureduce
não está funcionando como você pensa, considere umStream
que contenhanull
elementos, talvez você pense que o cobriu, mas eu posso estar[User#1, null, User#2, null, User#3]
, agora não lançará uma exceção, a menos que eu esteja enganado aqui.null
para a função de redução, remover o argumento do valor da identidade tornaria obsoleto todo o tratonull
na função:reduce( (u,v) -> { throw new IllegalStateException("More than one ID found"); } )
faz o trabalho e, melhor ainda, ele já retorna umOptional
, eliminando a necessidade de chamarOptional.ofNullable
o resultado.Uma alternativa é usar a redução: (este exemplo usa seqüências de caracteres, mas pode ser facilmente aplicado a qualquer tipo de objeto, inclusive
User
)Então, para o caso com
User
você, você teria:fonte
Usando reduzir
Esta é a maneira mais simples e flexível que encontrei (com base na resposta @prunge)
Dessa forma, você obtém:
Optional.empty()
se não estiver presentefonte
Eu acho que dessa maneira é mais simples:
fonte
Usando um
Collector
:Uso:
Retornamos um
Optional
, já que geralmente não podemos assumirCollection
que ele contenha exatamente um elemento. Se você já sabe que é esse o caso, ligue para:Isso coloca o ônus de tratar o erro no chamador - como deveria.
fonte
Goiaba tem um
Collector
para isso chamadoMoreCollectors.onlyElement()
.fonte
Podemos usar RxJava ( biblioteca de extensão reativa muito poderosa )
O operador único lança uma exceção se nenhum usuário ou mais de um usuário for encontrado.
fonte
Como
Collectors.toMap(keyMapper, valueMapper)
usa uma fusão de lançamento para lidar com várias entradas com a mesma chave, é fácil:Você receberá um
IllegalStateException
para chaves duplicadas. Mas no final não tenho certeza se o código não seria ainda mais legível usando umif
.fonte
.collect(Collectors.toMap(user -> "", Function.identity())).get("")
, você tem um comportamento mais genérico.Estou usando esses dois colecionadores:
fonte
onlyOne()
lançaIllegalStateException
para> 1 elementos e NoSuchElementException` (inOptional::get
) para 0 elementos.Supplier
de(Runtime)Exception
.Se você não se importa em usar uma biblioteca de terceiros, tanto
SequenceM
do cyclops-streams (comoLazyFutureStream
do simple- react), ambos têm operadores únicos e únicos opcionais.singleOptional()
lança uma exceção se houver0
ou mais de1
elementos noStream
, caso contrário, ele retornará o valor único.singleOptional()
retornaOptional.empty()
se não houver valores ou mais de um valor noStream
.Divulgação - sou o autor de ambas as bibliotecas.
fonte
Eu fui com a abordagem direta e apenas implementei a coisa:
com o teste JUnit:
Esta implementação não é segura.
fonte
fonte
Você já tentou isso?
Fonte: https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html
fonte
count()
não é bom usar porque é uma operação terminal.