Como depurar stream (). Map (…) com expressões lambda?

Em nosso projeto, estamos migrando para o java 8 e estamos testando os novos resources dele.

No meu projeto, estou usando predicados e funções do Guava para filtrar e transformar algumas collections usando Collections2.transform e Collections2.filter .

Nessa migration, preciso alterar, por exemplo, o código de goiaba para as alterações do java 8. Então, as mudanças que estou fazendo são do tipo:

 List naturals = Lists.newArrayList(1,2,3,4,5,6,7,8,9,10,11,12,13); Function  duplicate = new Function(){ @Override public Integer apply(Integer n) { return n * 2; } }; Collection result = Collections2.transform(naturals, duplicate); 

Para…

 List result2 = naturals.stream() .map(n -> n * 2) .collect(Collectors.toList()); 

Usando o goava fiquei muito confortável depurando o código já que eu poderia depurar cada processo de transformação mas minha preocupação é como depurar por exemplo .map(n -> n*2) .

Usando o depurador eu posso ver algum código como:

 @Hidden @DontInline /** Interpretively invoke this form on the given arguments. */ Object interpretWithArguments(Object... argumentValues) throws Throwable { if (TRACE_INTERPRETER) return interpretWithArgumentsTracing(argumentValues); checkInvocationCounter(); assert(arityCheck(argumentValues)); Object[] values = Arrays.copyOf(argumentValues, names.length); for (int i = argumentValues.length; i < values.length; i++) { values[i] = interpretName(names[i], values); } return (result < 0) ? null : values[result]; } 

Mas não é tão direto quanto o Guava depurar o código, na verdade não consegui encontrar a transformação n * 2 .

Existe uma maneira de ver essa transformação ou uma maneira de depurar facilmente esse código?

EDIT: eu adicionei resposta de diferentes comentários e respostas postadas

Graças ao comentário de Holger que respondeu à minha pergunta, a abordagem de ter o bloco lambda me permitiu ver o processo de transformação e depurar o que aconteceu dentro do corpo do lambda:

 .map( n -> { Integer nr = n * 2; return nr; } ) 

Graças a Stuart Marks a abordagem de ter referências a methods também me permitiu depurar o processo de transformação:

 static int timesTwo(int n) { Integer result = n * 2; return result; } ... List result2 = naturals.stream() .map(Java8Test::timesTwo) .collect(Collectors.toList()); ... 

Graças à resposta de Marlon Bernardes , notei que o meu Eclipse não mostra o que deveria e o uso de peek () ajudou a mostrar os resultados.

Eu normalmente não tenho nenhum problema na debugging de expressões lambda enquanto uso o Eclipse Kepler ou o Intellij IDEA (usando o JDK8u5). Basta definir um ponto de interrupção e certifique-se de não inspecionar a expressão lambda inteira (inspecione apenas o corpo lambda).

Depurando Lambdas

Outra abordagem é usar a peek para inspecionar os elementos do stream:

 List naturals = Arrays.asList(1,2,3,4,5,6,7,8,9,10,11,12,13); naturals.stream() .map(n -> n * 2) .peek(System.out::println) .collect(Collectors.toList()); 

ATUALIZAR:

Eu acho que você está ficando confuso porque o map é uma intermediate operation – em outras palavras: é uma operação preguiçosa que será executada somente após a terminal operation ser executada. Então quando você chama stream.map(n -> n * 2) o corpo lambda não está sendo executado no momento. Você precisa definir um ponto de interrupção e inspecioná-lo após a operação do terminal ( collect , neste caso).

Verifique as operações do stream para mais explicações.

ATUALIZAÇÃO 2:

Citando o comentário de Holger :

O que torna complicado aqui é que a chamada para mapear e a expressão lambda estão em uma linha, portanto, um ponto de interrupção de linha será interrompido em duas ações completamente não relacionadas.

Inserir uma quebra de linha logo após o map( permite definir um ponto de interrupção apenas para a expressão lambda. E não é incomum que os depuradores não mostrem valores intermediários de uma instrução de return . Alterando o lambda para n -> { int result=n * 2; return result; } permitiria que você inspecionasse o resultado. Novamente, insira as quebras de linha apropriadamente quando pisa linha por linha…

O IntelliJ tem um plugin tão bom para este caso quanto um plugin Java Stream Debugger . Eu proponho que você olhe para este: https://plugins.jetbrains.com/plugin/9696-java-stream-debugger?platform=hootsuite

Ele estende a janela da ferramenta Depurador IDEA adicionando o botão Trace Current Stream Chain, que se torna ativo quando o depurador para dentro de uma cadeia de chamadas da API do Stream.

Tem boa interface para trabalhar com operações de streams separados e lhe dá a oportunidade de seguir alguns valores que você deve depurar.

Depurador de Fluxo Java

A debugging de lambdas também funciona bem com o NetBeans. Estou usando o NetBeans 8 e o JDK 8u5.

Se você definir um ponto de interrupção em uma linha onde há um lambda, você realmente atingirá uma vez quando o pipeline estiver configurado e, em seguida, uma vez para cada elemento de stream. Usando seu exemplo, a primeira vez que você atingir o ponto de interrupção será a chamada de map() que está configurando o stream de stream:

primeiro ponto de interrupção

Você pode ver a pilha de chamadas e as variables ​​locais e valores de parâmetros para main como seria de esperar. Se você continuar pisando, o ponto de interrupção “mesmo” é atingido novamente, exceto que desta vez é dentro da chamada para o lambda:

insira a descrição da imagem aqui

Observe que, desta vez, a pilha de chamadas é profunda na maquinaria de streams e as variables ​​locais são os locais do próprio lambda, não o método main fechamento. (Eu mudei os valores na lista de naturals para deixar isso claro).

Como Marlon Bernardes apontou (+1), você pode usar a peek para inspecionar os valores conforme eles passam no pipeline. Tenha cuidado, porém, se você estiver usando isso de um stream paralelo. Os valores podem ser impressos em uma ordem imprevisível em diferentes segmentos. Se você estiver armazenando valores em uma estrutura de dados de debugging do peek , essa estrutura de dados terá, é claro, que ser thread-safe.

Finalmente, se você estiver fazendo muita debugging de lambdas (especialmente lambdas de instrução de várias linhas), pode ser preferível extrair o lambda em um método nomeado e, em seguida, referenciá-lo usando uma referência de método. Por exemplo,

 static int timesTwo(int n) { return n * 2; } public static void main(String[] args) { List naturals = Arrays.asList(3247,92837,123); List result = naturals.stream() .map(DebugLambda::timesTwo) .collect(toList()); } 

Isso pode facilitar a visualização do que está acontecendo enquanto você está depurando. Além disso, extrair methods dessa maneira facilita o teste de unidade. Se o seu lambda é tão complicado que você precisa passar por ele, provavelmente quer ter um monte de testes unitários para ele de qualquer forma.

Intellij IDEA 15 parece tornar ainda mais fácil, permite parar em uma parte da linha onde lambda é, ver o primeiro recurso: http://blog.jetbrains.com/idea/2015/06/intellij-idea-15 -eap-is-open /