Como peek () e allMatch () funcionam juntos na Java 8 Stream API

10

Encontrei um questionário sobre a API do Java 8 Stream do método peek, conforme abaixo

Arrays.asList("Fred", "Jim", "Sheila")
      .stream()
      .peek(System.out::println)
      .allMatch(s -> s.startsWith("F"));

A saída é

Fred
Jim

Estou confuso como esse fluxo funciona? Meu resultado esperado deve ser

Fred
Jim
Sheila

O método peek () é uma operação intermediária e processa cada elemento no Stream. Alguém pode me explicar isso.

Barcelona
fonte

Respostas:

10

É uma otimização de fluxo conhecida como curto-circuito. Essencialmente, o que acontece é que allMatchimpede a execução de operações intermediárias desnecessárias no fluxo, porque não faz sentido realizá-las quando o resultado final é conhecido.

É como se isso tivesse acontecido:

take"Fred"
peek("Fred")
evaluate("Fred".startsWith("F"))
decide whether the result of allMatch() is known for sure: Not yet

take"Jim"
peek("Jim")
evaluate("Jim".startsWith("F"))
decide whether the result of allMatch() is known for sure: Yes

Quando "Jim".startsWith("F")é avaliado, o resultado de allMatch(s -> s.startsWith("F"))é conhecido com certeza. Não importa que valores entrem no pipeline depois "Jim", sabemos que todos os valores começam com "F" são falsos

Isso não é específico para a combinação peek/ allMatch, existem várias operações de curto-circuito intermediárias e terminais. java.util.streamOs documentos do pacote afirmam:

Além disso, algumas operações são consideradas operações de curto-circuito. Uma operação intermediária está em curto-circuito se, quando apresentada com entrada infinita, pode produzir um fluxo finito como resultado. Uma operação do terminal está em curto-circuito se, quando apresentada com entrada infinita, pode terminar em tempo finito. Ter uma operação em curto-circuito na tubulação é uma condição necessária, mas não suficiente, para que o processamento de um fluxo infinito termine normalmente em tempo finito.

Estenda isso para fluxos finitos e as operações de curto-circuito impedem a execução de etapas desnecessárias do pipeline, como no caso do seu exemplo.

ernest_k
fonte
5
Arrays.asList("Fred", "Jim", "Sheila")
      .stream()
      .peek(System.out::println)
      .allMatch(s -> s.startsWith("F"));
  • Pela primeira vez, Fredé impresso. Corresponde tão
  • Segunda vez, Jimé impresso. Não corresponde, pelo que o allMatch termina porque "Todos não corresponderam"
  • Portanto, o último item não foi consumido do fluxo.
WJS
fonte
3

Os documentos para o peekmétodo dizem (ênfase minha):

Retorna um fluxo que consiste nos elementos desse fluxo, executando adicionalmente a ação fornecida em cada elemento à medida que os elementos são consumidos no fluxo resultante .

Portanto, neste caso, peeknão vê "Sheila"porque esse valor não é consumido do fluxo. Assim que "Jim"consumido, o resultado de .allMatch(s -> s.startsWith("F"))já é conhecido false, portanto, não há necessidade de consumir mais elementos do fluxo.

kaya3
fonte
1

Conforme Java Doc Of allMatch ():

Retorna se todos os elementos desse fluxo correspondem ao predicado fornecido. Pode não avaliar o predicado em todos os elementos se não for necessário para determinar o resultado. Se o fluxo estiver vazio, {@code true} será retornado e o predicado não será avaliado.

@apiNote

Este método avalia a quantificação universal do predicado sobre os elementos do fluxo (para todos x P (x)). Se o fluxo estiver vazio, a quantificação será satisfeita de forma vaga e sempre será {@code true} (independentemente de P (x)).

predicado a aplicar aos elementos deste fluxo @return {@code true} se todos os elementos do fluxo corresponderem ao predicado fornecido ou o fluxo estiver vazio, caso contrário, {@code false}

No seu caso:

1-

p(x) : s -> s.startsWith("F")

X : "Fred"

result : X P(X) = true

2-

p(x) : s -> s.startsWith("F")

X : "Jim"

result : X P(X) = false

Nenhuma avaliação adicional ocorrerá, porque XP (X) = false

boolean result = Arrays.asList("Fred", "Finda", "Fish")
            .stream()
            .peek(System.out::println)
            .allMatch(s -> s.startsWith("F"));
    System.out.println("Result "+result);

A saída é:

Fred
Finda
Fish
Result true

Aqui o fluxo é processado completamente porque xP (x) = true de cada elemento

Sandeep Tiwari
fonte