Come eseguire il debug di stream (). Map (…) con espressioni lambda?


114

Nel nostro progetto stiamo migrando a java 8 e ne stiamo testando le nuove funzionalità.

Nel mio progetto sto usando predicati e funzioni Guava per filtrare e trasformare alcune raccolte usando Collections2.transforme Collections2.filter.

In questa migrazione ho bisogno di cambiare ad esempio il codice guava in Java 8 modifiche. Quindi, i cambiamenti che sto facendo sono il tipo di:

List<Integer> naturals = Lists.newArrayList(1,2,3,4,5,6,7,8,9,10,11,12,13);

Function <Integer, Integer> duplicate = new Function<Integer, Integer>(){
    @Override
    public Integer apply(Integer n)
    {
        return n * 2;
    }
};

Collection result = Collections2.transform(naturals, duplicate);

Per...

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

Usando guava sono stato molto a mio agio nel debug del codice poiché potevo eseguire il debug di ogni processo di trasformazione, ma la mia preoccupazione è come eseguire il debug, ad esempio .map(n -> n*2).

Usando il debugger posso vedere del codice come:

@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];
}

Ma non è così semplice come Guava eseguire il debug del codice, in realtà non sono riuscito a trovare la n * 2trasformazione.

C'è un modo per vedere questa trasformazione o un modo per eseguire facilmente il debug di questo codice?

EDIT: ho aggiunto la risposta da diversi commenti e le risposte pubblicate

Grazie al Holgercommento che ha risposto alla mia domanda, l'approccio di avere il blocco lambda mi ha permesso di vedere il processo di trasformazione ed eseguire il debug di ciò che è accaduto all'interno del corpo lambda:

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

Grazie Stuart Marksall'approccio di avere riferimenti al metodo mi ha anche permesso di eseguire il debug del processo di trasformazione:

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

Grazie alla Marlon Bernardesrisposta ho notato che il mio Eclipse non mostra quello che dovrebbe e l'uso di peek () ha aiutato a visualizzare i risultati.


Non è necessario dichiarare la resultvariabile temporanea come Integer. Un semplice intdovrebbe andare bene anche se stai facendo mapun ping inta un int...
Holger

Inoltre aggiungo che c'è il debugger migliorato in IntelliJ IDEA 14. Ora possiamo eseguire il debug di Lamdas.
Mikhail

Risposte:


86

Di solito non ho problemi a eseguire il debug delle espressioni lambda durante l'utilizzo di Eclipse o IntelliJ IDEA. Basta impostare un punto di interruzione e assicurarsi di non ispezionare l'intera espressione lambda (ispezionare solo il corpo lambda).

Debug di Lambda

Un altro approccio è quello di utilizzare peekper ispezionare gli elementi del flusso:

List<Integer> 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());

AGGIORNARE:

Penso che ti stai confondendo perché mapè un intermediate operation- in altre parole: è un'operazione pigra che verrà eseguita solo dopo che a è terminal operationstato eseguito. Quindi quando chiami stream.map(n -> n * 2)il corpo lambda non viene eseguito al momento. È necessario impostare un punto di interruzione e ispezionarlo dopo che è stata chiamata un'operazione sul terminale ( collect, in questo caso).

Controllare le operazioni di flusso per ulteriori spiegazioni.

AGGIORNAMENTO 2:

Citando il commento di Holger :

Ciò che rende complicato qui è che la chiamata a mappare e l'espressione lambda sono in una riga, quindi un punto di interruzione di riga si fermerà su due azioni completamente non correlate.

L'inserimento di un'interruzione di riga subito dopo map( consente di impostare un punto di interruzione solo per l'espressione lambda. E non è insolito che i debugger non mostrino i valori intermedi di returnun'istruzione. Cambiare lambda in n -> { int result=n * 2; return result; } consentirebbe di controllare il risultato. Di nuovo, inserisci le interruzioni di riga in modo appropriato quando passi riga per riga ...


Grazie per la schermata di stampa. Quale versione di Eclipse hai o cosa hai fatto per ottenere quella finestra di dialogo? Ho provato a usare inspecte display e ottenere n cannot be resolved to a variable. A proposito, anche il peek è utile ma stampa tutti i valori contemporaneamente. Voglio vedere ogni processo di iterazione per controllare la trasformazione. È possibile?
Federico Piazza

Sto usando Eclipse Kepler SR2 (con supporto Java 8 installato dal marketplace di Eclipse).
Marlon Bernardes

Stai usando anche Eclipse? Basta impostare un punto di interruzione in .maplinea e premere F8 più volte.
Marlon Bernardes

6
@Fede: ciò che rende complicato qui è che la chiamata a mape l'espressione lambda sono in una riga, quindi un punto di interruzione di riga si fermerà su due azioni completamente non correlate. L'inserimento di un'interruzione di riga subito dopo map(consente di impostare un punto di interruzione solo per l'espressione lambda. E non è insolito che i debugger non mostrino i valori intermedi di returnun'istruzione. Cambiare lambda in n -> { int result=n * 2; return result; }consentirebbe di ispezionare result. Di nuovo, inserisci le interruzioni di riga in modo appropriato quando passi riga per riga ...
Holger

1
@Marlon Bernardes: certo, puoi aggiungerlo alla risposta perché questo è lo scopo dei commenti: aiutare a migliorare il contenuto. A proposito, ho modificato il testo citato aggiungendo la formattazione del codice ...
Holger

33

IntelliJ ha un bel plugin per questo caso come un plugin Java Stream Debugger . Dovresti verificarlo: https://plugins.jetbrains.com/plugin/9696-java-stream-debugger?platform=hootsuite

Estende la finestra dello strumento IDEA Debugger aggiungendo il pulsante Trace Current Stream Chain, che diventa attivo quando il debugger si ferma all'interno di una catena di chiamate Stream API.

Ha una bella interfaccia per lavorare con operazioni di flussi separati e ti dà l'opportunità di seguire alcuni valori di cui dovresti eseguire il debug.

Java Stream Debugger

Puoi avviarlo manualmente dalla finestra Debug facendo clic qui:

inserisci qui la descrizione dell'immagine


23

Anche il debug di lambda funziona bene con NetBeans. Sto usando NetBeans 8 e JDK 8u5.

Se imposti un punto di interruzione su una riga in cui è presente un lambda, in realtà verrà raggiunto una volta quando la pipeline è configurata e quindi una volta per ogni elemento del flusso. Usando il tuo esempio, la prima volta che raggiungerai il punto di interruzione sarà la map()chiamata che sta configurando la pipeline del flusso:

primo punto di interruzione

Puoi vedere lo stack di chiamate e le variabili locali e i valori dei parametri maincome ti aspetteresti. Se continui a fare un passo, lo "stesso" punto di interruzione viene raggiunto di nuovo, tranne che questa volta è all'interno della chiamata al lambda:

inserisci qui la descrizione dell'immagine

Si noti che questa volta lo stack di chiamate è nel profondo del meccanismo dei flussi e le variabili locali sono le variabili locali del lambda stesso, non il mainmetodo di inclusione . (Ho modificato i valori naturalsnell'elenco per renderlo più chiaro.)

Come ha sottolineato Marlon Bernardes (+1), è possibile utilizzare peekper ispezionare i valori mentre passano nella pipeline. Fai attenzione però se lo stai usando da un flusso parallelo. I valori possono essere stampati in un ordine imprevedibile su diversi thread. Se stai archiviando valori in una struttura di dati di debug da peek, quella struttura di dati dovrà ovviamente essere thread-safe.

Infine, se stai eseguendo molti debug di lambda (in particolare lambda di istruzioni su più righe), potrebbe essere preferibile estrarre il lambda in un metodo denominato e quindi fare riferimento ad esso utilizzando un riferimento al metodo. Per esempio,

static int timesTwo(int n) {
    return n * 2;
}

public static void main(String[] args) {
    List<Integer> naturals = Arrays.asList(3247,92837,123);
    List<Integer> result =
        naturals.stream()
            .map(DebugLambda::timesTwo)
            .collect(toList());
}

Questo potrebbe rendere più facile vedere cosa sta succedendo durante il debug. Inoltre, l'estrazione dei metodi in questo modo semplifica lo unit test. Se il tuo lambda è così complicato che devi eseguirlo in un solo passaggio, probabilmente vorrai comunque fare un sacco di unit test.


Il mio problema era che non potevo eseguire il debug del corpo lambda, ma il tuo approccio all'utilizzo dei riferimenti al metodo mi ha aiutato molto con ciò che volevo. Potresti aggiornare la tua risposta usando l'approccio Holger che ha funzionato perfettamente anche aggiungendo { int result=n * 2; return result; }righe diverse e potrei accettare la risposta poiché entrambe le risposte sono state utili. +1 ovviamente.
Federico Piazza

1
@Fede Sembra che l'altra risposta sia già stata aggiornata, quindi non è necessario aggiornare la mia. Odio comunque i lambda su più righe. :-)
Stuart Marks

1
@Stuart Marks: preferisco anche i lambda a riga singola. Quindi di solito rimuovo le interruzioni di riga dopo il debug "che si applica anche ad altre istruzioni composte (ordinarie)".
Holger

1
@Fede Nessun problema. È tua prerogativa in qualità di richiedente accettare la risposta che preferisci. Grazie per il +1.
Stuart Marks

1
Penso che la creazione di riferimenti a metodi, oltre a rendere i metodi più semplici per i test unitari, renda anche il codice più leggibile. Bella risposta! (+1)
Marlon Bernardes


6

Solo per fornire dettagli più aggiornati (ottobre 2019), IntelliJ ha aggiunto una bella integrazione per eseguire il debug di questo tipo di codice che è estremamente utile.

Quando ci fermiamo su una riga che contiene un lambda, se premiamo F7(entra), IntelliJ evidenzierà quale sarà lo snippet di cui eseguire il debug. Possiamo cambiare il blocco con cui eseguire il debug Tabe, una volta deciso, fare F7nuovamente clic .

Ecco alcuni screenshot per illustrare:

1- Premere il tasto F7(entra in), verranno visualizzati i punti salienti (o la modalità di selezione) inserisci qui la descrizione dell'immagine

2- Utilizzare Tabpiù volte per selezionare lo snippet di cui eseguire il debug inserisci qui la descrizione dell'immagine

3- Premere il tasto F7(entra in) per entrare inserisci qui la descrizione dell'immagine


1

Il debug utilizzando gli IDE è sempre utile, ma il modo ideale per eseguire il debug attraverso ogni elemento in uno stream è usare peek () prima di un'operazione del metodo terminale poiché Java Steam viene valutato pigramente, quindi a meno che non venga invocato un metodo terminale, il rispettivo stream lo farà non essere valutato.

List<Integer> numFromZeroToTen = Arrays.asList(1,2,3,4,5,6,7,8,9,10);

    numFromZeroToTen.stream()
        .map(n -> n * 2)
        .peek(n -> System.out.println(n))
        .collect(Collectors.toList());
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.