Suponha que eu tenha um fluxo de coisas e que queira "enriquecê-los" no meio do fluxo, posso usar peek()
isso, por exemplo:
streamOfThings.peek(this::thingMutator).forEach(this::someConsumer);
Suponha que a alteração das coisas neste momento no código seja um comportamento correto - por exemplo, o thingMutator
método pode definir o campo "lastProcessed" para o horário atual.
No entanto, peek()
na maioria dos contextos significa "olhe, mas não toque".
O uso peek()
para alterar elementos de fluxo é um antipadrão ou é desaconselhável?
Editar:
A abordagem alternativa, mais convencional, seria converter o consumidor:
private void thingMutator(Thing thing) {
thing.setLastProcessed(System.currentTimeMillis());
}
para uma função que retorna o parâmetro:
private Thing thingMutator(Thing thing) {
thing.setLastProcessed(currentTimeMillis());
return thing;
}
e use map()
:
stream.map(this::thingMutator)...
Mas isso introduz código superficial (the return
) e não estou convencido de que seja mais claro, porque você sabe que peek()
retorna o mesmo objeto, mas com map()
isso nem fica claro à primeira vista que é a mesma classe de objeto.
Além disso, peek()
você pode ter um lambda que sofre mutação, mas com map()
você tem que construir um acidente de trem. Comparar:
stream.peek(t -> t.setLastProcessed(currentTimeMillis())).forEach(...)
stream.map(t -> {t.setLastProcessed(currentTimeMillis()); return t;}).forEach(...)
Eu acho que a peek()
versão é mais clara e o lambda está claramente mudando, então não há efeito colateral "misterioso". Da mesma forma, se uma referência de método for usada e o nome do método implicar claramente uma mutação, isso também será claro e óbvio.
Em uma nota pessoal, não evito usar o peek()
mutate - acho muito conveniente.
peek
com um fluxo que gera seus elementos dinamicamente? Ainda funciona ou as alterações são perdidas? Modificar os elementos de um fluxo parece pouco confiável para mim.List<Thing> list; things.stream().peek(list::add).forEach(...);
muito útil. Recentemente. Eu usei-o para adicionar informações para a publicação:Map<Thing, Long> timestamps = ...; return things.stream().peek(t -> t.setTimestamp(timestamp.get(t))).collect(toList());
. Eu sei que existem outras maneiras de fazer esse exemplo, mas estou simplificando bastante aqui. Usarpeek()
produz código IMHO mais compacto e mais elegante. Legibilidade à parte, esta questão é realmente sobre o que você levantou; é seguro / confiável?peek
? Tenho uma pergunta semelhante sobre o stackoverflow e espero que você possa ver e dar seu feedback. Obrigado. stackoverflow.com/questions/47356992/…Respostas:
Você está correto, "espiar" no sentido inglês da palavra significa "olhar, mas não toque".
No entanto, o JavaDoc afirma:
Palavras-chave: "realizando ... ação" e "consumido". O JavaDoc é muito claro que devemos esperar
peek
ter a capacidade de modificar o fluxo.No entanto, o JavaDoc também declara:
Isso indica que se destina mais à observação, por exemplo, elementos de registro no fluxo.
O que retiro de tudo isso é que podemos executar ações usando os elementos no fluxo, mas devemos evitar a mutação de elementos no fluxo. Por exemplo, vá em frente e chame métodos nos objetos, mas tente evitar operações mutantes neles.
No mínimo, gostaria de adicionar um breve comentário ao seu código ao longo destas linhas:
As opiniões divergem sobre a utilidade de tais comentários, mas eu usaria esse comentário neste caso.
fonte
thingMutator
, ou mais concretaresetLastProcessed
etc. A menos que haja um motivo convincente, os comentários necessários, como a sua sugestão, normalmente indicam más escolhas de nomes de variáveis e / ou métodos. Se bons nomes são escolhidos, não é suficiente? Ou você está dizendo que, apesar de um bom nome, qualquer coisapeek()
é como um ponto cego que a maioria dos programadores "roçava" (pula visualmente)? Além disso, "principalmente para depuração" não é a mesma coisa que "apenas para depuração" - que outros usos além dos "principalmente" foram destinados?Poderia ser facilmente mal interpretado, para evitar o uso assim. Provavelmente, a melhor opção é usar uma função lambda para mesclar as duas ações necessárias na chamada forEach. Você também pode considerar devolver um novo objeto em vez de alterar o existente - ele pode ser um pouco menos eficiente, mas provavelmente resulta em um código mais legível e reduz o potencial de usar acidentalmente a lista modificada para outra operação que deve receberam o original.
fonte
A nota da API informa que o método foi adicionado principalmente para ações como estatísticas de depuração / registro / disparo etc.
@apiNote Esse método existe principalmente para oferecer suporte à depuração, onde você deseja * ver os elementos à medida que eles fluem após um certo ponto em um pipeline: *
fonte