Encontre o primeiro elemento por predicado


504

Acabei de começar a jogar com o Java 8 lambdas e estou tentando implementar algumas das coisas com as quais estou acostumado nas linguagens funcionais.

Por exemplo, a maioria das linguagens funcionais possui algum tipo de função find que opera em seqüências ou listas que retornam o primeiro elemento, para o qual o predicado é true. A única maneira de conseguir isso no Java 8 é:

lst.stream()
    .filter(x -> x > 5)
    .findFirst()

No entanto, isso me parece ineficiente, pois o filtro varrerá a lista inteira, pelo menos para minha compreensão (o que pode estar errado). Existe uma maneira melhor?


53
Não é ineficiente, a implementação do Java 8 Stream é avaliada com preguiça; portanto, o filtro é aplicado apenas à operação do terminal. Mesma pergunta aqui: stackoverflow.com/questions/21219667/stream-and-lazy-evaluation
— Marek Gregor

1
Legal. Era o que eu esperava que fizesse. Caso contrário, teria sido um grande fracasso no design.
— siki

2
Se a sua intenção é realmente verificar se a lista contém esse elemento (e não apenas o primeiro de vários),.
— Joachim Lous

Comparado a um ciclo forEach simples, isso criaria muitos objetos no heap e dezenas de chamadas de método dinâmico. Embora isso nem sempre afete a linha de fundo em seus testes de desempenho, nos hot spots, faz diferença abster-se do uso trivial do Stream e de construções semelhantes de peso pesado.
— Agoston Horvath

Respostas:


720

Não, o filtro não varre o fluxo inteiro. É uma operação intermediária, que retorna um fluxo lento (na verdade, todas as operações intermediárias retornam um fluxo lento). Para convencê-lo, você pode simplesmente fazer o seguinte teste:

List<Integer> list = Arrays.asList(1, 10, 3, 7, 5);
int a = list.stream()
            .peek(num -> System.out.println("will filter " + num))
            .filter(x -> x > 5)
            .findFirst()
            .get();
System.out.println(a);

Quais saídas:

will filter 1
will filter 10
10

Você vê que apenas os dois primeiros elementos do fluxo são realmente processados.

Então você pode seguir sua abordagem, o que é perfeitamente correto.


37
Como observação, usei get();aqui porque sei quais valores alimentam o pipeline de fluxo e, portanto, haverá um resultado. Na prática, você não deve usar get();, mas orElse()/ orElseGet()/ orElseThrow()(para um erro mais significativo em vez de um NSEE), pois talvez você não saiba se as operações aplicadas ao pipeline de fluxo resultarão em um elemento.
— Alexis C.

31
.findFirst().orElse(null);por exemplo
— Gondy 11/11

20
Não use orElse null. Isso deve ser um anti-padrão. Está tudo incluído no opcional. Por que você deve arriscar um NPE? Eu acho que lidar com o opcional é a melhor maneira. Apenas teste o opcional com isPresent () antes de usá-lo.
— precisa saber é o seguinte

@ BeJay eu não entendo. o que devo usar em vez de orElse?
— John Henckel

3
@ JohnHenckel Acho que o que BeJay significa é que você deve deixá-lo como um Optionaltipo, que é o que .findFirstretorna. Um dos usos do Opcional é ajudar os desenvolvedores a evitar lidar com nullseg em vez de verificar myObject != null, você pode verificar myOptional.isPresent()ou usar outras partes da interface Opcional. Isso tornou mais claro?
— AMTerp

102

No entanto, isso me parece ineficiente, pois o filtro varrerá a lista inteira

Não, não vai - ele irá "quebrar" assim que o primeiro elemento que satisfizer o predicado for encontrado. Você pode ler mais sobre preguiça no pacote stream javadoc , em particular (ênfase minha):

Muitas operações de fluxo, como filtragem, mapeamento ou remoção duplicada, podem ser implementadas com preguiça, expondo oportunidades de otimização. Por exemplo, "encontre a primeira String com três vogais consecutivas" não precisa examinar todas as strings de entrada. As operações de fluxo são divididas em operações intermediárias (produção de fluxo) e operações terminais (produção de valor ou efeito colateral). Operações intermediárias são sempre preguiçosas.


5
Esta resposta foi mais informativa para mim e explica o porquê, não apenas como. Eu nunca novas operações intermediárias são sempre preguiçosas; Os fluxos Java continuam a me surpreender.
— Kevinarpe 6/07

30
return dataSource.getParkingLots()
                 .stream()
                 .filter(parkingLot -> Objects.equals(parkingLot.getId(), id))
                 .findFirst()
                 .orElse(null);

Eu tive que filtrar apenas um objeto de uma lista de objetos. Então eu usei isso, espero que ajude.


MELHOR: como procuramos um valor de retorno booleano, podemos fazer isso melhor adicionando verificação nula: return dataSource.getParkingLots (). Stream (). Filter (parkingLot -> Objects.equals (parkingLot.getId (), id)) .findFirst (). orElse (null)! = null;
— Shreedhar Bhat

1
@shreedharbhat Você não precisa fazer isso .orElse(null) != null. Em vez disso, use as APIs opcionais, .isPresentie .findFirst().isPresent().
— AMTerp

@shreedharbhat antes de tudo, o OP não estava procurando um valor de retorno booleano. Em segundo lugar, se fossem, seria mais limpo escrever.stream().map(ParkingLot::getId).anyMatch(Predicate.isEqual(id))
— AjaxLeung

13

Além da resposta de Alexis C , se você estiver trabalhando com uma lista de matrizes, na qual não tem certeza se o elemento que está procurando existe, use-o.

Integer a = list.stream()
                .peek(num -> System.out.println("will filter " + num))
                .filter(x -> x > 5)
                .findFirst()
                .orElse(null);

Então você pode simplesmente verificar se um é null.


1
Você deve corrigir o seu exemplo. Você não pode atribuir nulo a um int simples. stackoverflow.com/questions/2254435/can-an-int-be-null-in-java
— RubioRic

Eu editei sua postagem. 0 (zero) pode ser um resultado válido quando você estiver pesquisando em uma lista de números inteiros. Tipo de variável substituída por Inteiro e valor padrão por nulo.
— RubioRic

0

import org.junit.Test;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;

// Stream is ~30 times slower for same operation...
public class StreamPerfTest {

    int iterations = 100;
    List<Integer> list = Arrays.asList(1, 10, 3, 7, 5);


    // 55 ms
    @Test
    public void stream() {

        for (int i = 0; i < iterations; i++) {
            Optional<Integer> result = list.stream()
                    .filter(x -> x > 5)
                    .findFirst();

            System.out.println(result.orElse(null));
        }
    }

    // 2 ms
    @Test
    public void loop() {

        for (int i = 0; i < iterations; i++) {
            Integer result = null;
            for (Integer walk : list) {
                if (walk > 5) {
                    result = walk;
                    break;
                }
            }
            System.out.println(result);
        }
    }
}

0

Resposta aprimorada de uma linha: se você estiver procurando por um valor de retorno booleano, podemos fazer isso melhor adicionando isPresent:

return dataSource.getParkingLots().stream().filter(parkingLot -> Objects.equals(parkingLot.getId(), id)).findFirst().isPresent();

Se você deseja um valor de retorno booleano, use anyMatch
— AjaxLeung
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.