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?


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.


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.


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));


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.)

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.