Retornar de lambda forEach () em java

95

Estou tentando alterar alguns loops for-each para forEach()métodos lambda para descobrir as possibilidades das expressões lambda. O seguinte parece ser possível:

ArrayList<Player> playersOfTeam = new ArrayList<Player>();      
for (Player player : players) {
    if (player.getTeam().equals(teamName)) {
        playersOfTeam.add(player);
    }
}

Com lambda forEach()

players.forEach(player->{if (player.getTeam().equals(teamName)) {playersOfTeam.add(player);}});

Mas o próximo não funciona:

for (Player player : players) {
    if (player.getName().contains(name)) {
        return player;
    }
}

com lambda

players.forEach(player->{if (player.getName().contains(name)) {return player;}});

Há algo errado na sintaxe da última linha ou é impossível retornar do forEach()método?

Samutamm
fonte
Não estou muito familiarizado com o interior dos lambdas ainda, mas quando me pergunto: "De onde você estaria voltando?", Minha suspeita inicial seria de que não é o método.
Gimby
1
@Gimby Sim, returndentro de uma instrução lambda retorna do próprio lambda, não de qualquer coisa chamada lambda. Eles encerram um fluxo antecipadamente ("curto-circuito"), findFirstconforme mostrado na resposta de Ian Roberts .
Stuart Marks

Respostas:

118

O returnthere está retornando da expressão lambda em vez do método que o contém. Em vez de forEachvocê precisa para filtero fluxo:

players.stream().filter(player -> player.getName().contains(name))
       .findFirst().orElse(null);

Aqui, filterrestringe o fluxo aos itens que correspondem ao predicado e, em findFirstseguida, retorna um Optionalcom a primeira entrada correspondente.

Isso parece menos eficiente do que a abordagem for-loop, mas na verdade findFirst()pode causar curto-circuito - não gera todo o fluxo filtrado e, em seguida, extrai um elemento dele, em vez disso, filtra apenas quantos elementos precisa para encontre o primeiro correspondente. Você também pode usar em findAny()vez de findFirst()se não se preocupar necessariamente em obter o primeiro jogador correspondente do fluxo (ordenado), mas simplesmente qualquer item correspondente. Isso permite melhor eficiência quando há paralelismo envolvido.

Ian Roberts
fonte
Obrigado, era isso que eu estava procurando! Parece haver muitas novidades no Java8 para explorar :)
samutamm
10
Razoável, mas sugiro que você não use orElse(null)em um Optional. O ponto principal de Optionalé fornecer uma maneira de indicar a presença ou ausência de um valor em vez de sobrecarregar o nulo (o que leva a NPEs). Se você usá- optional.orElse(null)lo, ele recupera todos os problemas com nulos. Eu o usaria apenas se você não pudesse modificar o chamador e ele realmente estivesse esperando um nulo.
Stuart Marks
1
@StuartMarks de fato, modificar o tipo de retorno do método para Optional<Player>seria uma maneira mais natural de se ajustar ao paradigma de streams. Eu estava apenas tentando mostrar como duplicar o comportamento existente usando lambdas.
Ian Roberts
for (Part part: parts) if (! part.isEmpty ()) return false; Eu me pergunto o que é realmente mais curto. E mais claro. A API de fluxo java realmente destruiu a linguagem e o ambiente java. Pesadelo para trabalhar em qualquer projeto java em 2020.
mmm
15

Eu sugiro que você primeiro tente entender o Java 8 como um todo, o mais importante no seu caso serão streams, lambdas e referências de método.

Você nunca deve converter o código existente em código Java 8 linha por linha, você deve extrair recursos e convertê-los.

O que identifiquei em seu primeiro caso é o seguinte:

  • Você deseja adicionar elementos de uma estrutura de entrada a uma lista de saída se eles corresponderem a algum predicado.

Vamos ver como fazemos isso, podemos fazer com o seguinte:

List<Player> playersOfTeam = players.stream()
    .filter(player -> player.getTeam().equals(teamName))
    .collect(Collectors.toList());

O que você faz aqui é:

  1. Transforme sua estrutura de entrada em um fluxo (estou assumindo aqui que é do tipo Collection<Player>, agora você tem um Stream<Player>.
  2. Filtre todos os elementos indesejados com um Predicate<Player>, mapeando cada jogador para o verdadeiro booleano, se desejar mantê-lo.
  3. Colete os elementos resultantes em uma lista, por meio de um Collector, aqui podemos usar um dos coletores de biblioteca padrão, que é Collectors.toList().

Isso também incorpora dois outros pontos:

  1. Código contra interfaces, portanto, código contra List<E>over ArrayList<E>.
  2. Use a inferência de diamante para o parâmetro de tipo em new ArrayList<>(), você está usando Java 8 afinal.

Agora em seu segundo ponto:

Você deseja novamente converter algo de legado Java em Java 8 sem olhar o quadro geral. Esta parte já foi respondida por @IanRoberts , embora eu ache que você precisa refazerplayers.stream().filter(...)... o que ele sugeriu.

skiwi
fonte
5

Se quiser retornar um valor booleano, você pode usar algo assim (muito mais rápido do que filtrar):

players.stream().anyMatch(player -> player.getName().contains(name));
Sriram
fonte
4

Isso é o que me ajudou:

List<RepositoryFile> fileList = response.getRepositoryFileList();
RepositoryFile file1 = fileList.stream().filter(f -> f.getName().contains("my-file.txt")).findFirst().orElse(null);

Retirado do Java 8 Finding Specific Element in List with Lambda

user5495300
fonte
1

Você também pode lançar uma exceção:

Nota:

Por uma questão de legibilidade, cada etapa do fluxo deve ser listada em uma nova linha.

players.stream()
       .filter(player -> player.getName().contains(name))
       .findFirst()
       .orElseThrow(MyCustomRuntimeException::new);

se sua lógica é vagamente "orientada por exceções", como se houver um lugar em seu código que captura todas as exceções e decide o que fazer a seguir. Use o desenvolvimento orientado por exceções apenas quando puder evitar sobrecarregar sua base de código com múltiplos try-catche lançar essas exceções são para casos muito especiais que você espera e podem ser tratados de forma adequada.)

Nabster
fonte