Acabei de me deparar com uma dúvida ao usar um List
e seu stream()
método. Embora eu saiba como usá-los, não tenho certeza sobre quando usá-los.
Por exemplo, tenho uma lista contendo vários caminhos para locais diferentes. Agora, gostaria de verificar se um único caminho determinado contém algum dos caminhos especificados na lista. Eu gostaria de retornar um com boolean
base no fato de a condição ter sido atendida ou não.
Isso, claro, não é uma tarefa difícil em si. Mas eu me pergunto se devo usar streams ou um loop for (-each).
A lista
private static final List<String> EXCLUDE_PATHS = Arrays.asList(new String[]{
"my/path/one",
"my/path/two"
});
Exemplo - Stream
private boolean isExcluded(String path){
return EXCLUDE_PATHS.stream()
.map(String::toLowerCase)
.filter(path::contains)
.collect(Collectors.toList())
.size() > 0;
}
Exemplo - For-Each Loop
private boolean isExcluded(String path){
for (String excludePath : EXCLUDE_PATHS) {
if(path.contains(excludePath.toLowerCase())){
return true;
}
}
return false;
}
Observe que o path
parâmetro está sempre em minúsculas .
Meu primeiro palpite é que a abordagem para cada é mais rápida, porque o loop retornaria imediatamente, se a condição fosse atendida. Ao passo que o fluxo ainda faria um loop em todas as entradas da lista para concluir a filtragem.
Minha suposição está correta? Se sim, por que (ou melhor, quando ) eu usaria stream()
então?
fonte
new String[]{…}
aqui. Basta usarArrays.asList("my/path/one", "my/path/two")
String[]
, não há necessidade de ligarArrays.asList
. Você pode simplesmente transmitir sobre a matriz usandoArrays.stream(array)
. A propósito, tenho dificuldade em entender o propósito doisExcluded
teste como um todo. É realmente interessante se um elemento deEXCLUDE_PATHS
está literalmente contido em algum lugar do caminho? Ou seja,isExcluded("my/path/one/foo/bar/baz")
vai voltartrue
, assim comoisExcluded("foo/bar/baz/my/path/one/")
...Arrays.stream
método, obrigado por apontar isso. Na verdade, o exemplo que postei parece bastante inútil para qualquer outra pessoa além de mim. Estou ciente do comportamento doisExcluded
método, mas na verdade é apenas algo de que preciso para mim, portanto, para responder à sua pergunta: sim , é interessante por motivos que gostaria de não mencionar, pois não caberia no escopo da pergunta original.toLowerCase
aplicado à constante que já está em minúsculas? Não deveria ser aplicado aopath
argumento?Respostas:
Sua suposição está correta. Sua implementação de stream é mais lenta do que o loop for.
Este uso de stream deve ser tão rápido quanto o for-loop embora:
Isso itera através dos itens, aplicando
String::toLowerCase
e o filtro aos itens um por um e terminando no primeiro item que corresponda.Ambos
collect()
eanyMatch()
são operações de terminal.anyMatch()
sai no primeiro item encontrado, entretanto, enquantocollect()
requer que todos os itens sejam processados.fonte
findFirst()
em combinação comfilter()
. Aparentemente, não sei usar streams tão bem quanto pensei.A decisão de usar Streams ou não deve ser orientada pela consideração de desempenho, mas sim pela legibilidade. Quando se trata de desempenho, existem outras considerações.
Com a sua
.filter(path::contains).collect(Collectors.toList()).size() > 0
abordagem, você está processando todos os elementos e coletando-os em um temporárioList
, antes de comparar o tamanho, ainda assim, isso dificilmente importa para um Stream composto de dois elementos.O uso
.map(String::toLowerCase).anyMatch(path::contains)
pode economizar memória e ciclos de CPU, se você tiver um número substancialmente maior de elementos. Ainda assim, isso converte cada umString
em sua representação em minúsculas, até que uma correspondência seja encontrada. Obviamente, vale a pena usarem vez de. Assim, você não precisa repetir a conversão para letras minúsculas em cada invocação de
isExcluded
. Se o número de elementosEXCLUDE_PATHS
ou o comprimento das strings se tornarem muito grandes, você pode considerar o usoCompilar uma string como padrão regex com o
LITERAL
sinalizador faz com que ela se comporte como operações de string comuns, mas permite que o mecanismo passe algum tempo em preparação, por exemplo, usando o algoritmo de Boyer Moore, para ser mais eficiente quando se trata da comparação real.Claro, isso só compensa se houver testes subsequentes suficientes para compensar o tempo gasto na preparação. Determinar se esse será o caso é uma das considerações reais de desempenho, além da primeira questão se essa operação algum dia será crítica para o desempenho. Não é a questão de usar Streams ou
for
loops.A propósito, os exemplos de código acima mantêm a lógica do seu código original, o que me parece questionável. Seu
isExcluded
método retornatrue
, se o caminho especificado contiver qualquer um dos elementos na lista, então ele retornatrue
para/some/prefix/to/my/path/one
, bem comomy/path/one/and/some/suffix
ou até mesmo/some/prefix/to/my/path/one/and/some/suffix
.Even
dummy/path/onerous
é considerado cumprindo os critérios, pois écontains
a stringmy/path/one
...fonte
Sim. Você está certo. Sua abordagem de fluxo terá alguma sobrecarga. Mas você pode usar tal construção:
O principal motivo para usar streams é que eles tornam seu código mais simples e fácil de ler.
fonte
anyMatch
um atalho parafilter(...).findFirst().isPresent()
?O objetivo dos streams em Java é simplificar a complexidade de escrever código paralelo. É inspirado na programação funcional. O fluxo serial serve apenas para tornar o código mais limpo.
Se quisermos desempenho, devemos usar parallelStream, que foi projetado para. O serial, em geral, é mais lento.
Há um bom artigo para ler sobre , e Desempenho
ForLoop
Stream
ParallelStream
.Em seu código, podemos usar métodos de terminação para interromper a pesquisa na primeira correspondência. (anyMatch ...)
fonte
Como outros mencionaram muitos pontos positivos, mas quero apenas mencionar a avaliação preguiçosa na avaliação do fluxo. Quando fazemos
map()
para criar um fluxo de caminhos em minúsculas, não estamos criando todo o fluxo imediatamente, em vez disso, o fluxo é construído lentamente , razão pela qual o desempenho deve ser equivalente ao tradicional loop for. Não está fazendo uma varredura completamap()
eanyMatch()
é executado ao mesmo tempo. QuandoanyMatch()
retornar verdadeiro, ele entrará em curto-circuito.fonte