it-swarm.com.de

Wie kann man stream (). Map (...) mit Lambda-Ausdrücken debuggen?

In unserem Projekt migrieren wir nach Java 8 und testen die neuen Funktionen.

In meinem Projekt verwende ich Guava-Prädikate und -Funktionen, um einige Sammlungen mit Collections2.transform Und Collections2.filter Zu filtern und zu transformieren.

Bei dieser Migration muss ich beispielsweise den Guava-Code in Java 8) ändern. Die Änderungen, die ich vornehme, sind also die folgenden:

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

Zu...

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

Unter Verwendung von Guava war es sehr einfach, den Code zu debuggen, da ich jeden Transformationsprozess debuggen konnte, aber meine Sorge ist, wie ich zum Beispiel .map(n -> n*2) debuggen kann.

Mit dem Debugger kann ich folgenden Code sehen:

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

Aber es ist nicht so einfach wie Guava, den Code zu debuggen, eigentlich konnte ich die Transformation n * 2 Nicht finden.

Gibt es eine Möglichkeit, diese Transformation zu sehen oder diesen Code einfach zu debuggen?

BEARBEITEN: Ich habe Antworten aus verschiedenen Kommentaren hinzugefügt und Antworten gepostet

Dank des Kommentars Holger, der meine Frage beantwortete, ermöglichte mir der Ansatz, Lambda-Block zu haben, den Transformationsprozess zu sehen und zu debuggen, was im Lambda-Körper passiert ist:

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

Dank Stuart Marks Konnte ich auch den Transformationsprozess debuggen, wenn ich Methodenreferenzen hatte:

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

Dank der Antwort Marlon Bernardes Habe ich festgestellt, dass meine Eclipse nicht anzeigt, was sie soll, und die Verwendung von peek () hat dazu beigetragen, dass die Ergebnisse angezeigt werden.

97
Federico Piazza

Normalerweise habe ich kein Problem damit, Lambda-Ausdrücke zu debuggen, während ich Eclipse oder IntelliJ IDEA verwende. Setzen Sie einfach einen Haltepunkt und überprüfen Sie nicht den gesamten Lambda-Ausdruck (überprüfen Sie nur den Lambda-Körper).

Debugging Lambdas

Ein anderer Ansatz besteht darin, peek zu verwenden, um die Elemente des Streams zu untersuchen:

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

UPDATE:

Ich denke, Sie werden verwirrt, weil map ein intermediate operation Ist - mit anderen Worten: Es ist eine verzögerte Operation, die erst ausgeführt wird, nachdem ein terminal operation Ausgeführt wurde. Wenn Sie also stream.map(n -> n * 2) aufrufen, wird der Lambda-Body momentan nicht ausgeführt. Sie müssen einen Haltepunkt festlegen und nach dem Aufrufen einer Terminaloperation überprüfen (in diesem Fall collect).

Aktivieren Sie Stream Operations für weitere Erklärungen.

UPDATE 2:

Zitat Holgers Kommentar:

Was es hier schwierig macht, ist, dass sich der Aufruf von map und der Lambda-Ausdruck in einer Zeile befinden, sodass ein Zeilenumbruchpunkt bei zwei völlig unabhängigen Aktionen stoppt.

Wenn Sie einen Zeilenumbruch direkt nach map( Einfügen, können Sie einen Unterbrechungspunkt nur für den Lambda-Ausdruck festlegen. Und es ist nicht ungewöhnlich, dass Debugger keine Zwischenwerte einer return -Anweisung anzeigen. Wenn Sie das Lambda in n -> { int result=n * 2; return result; } Ändern, können Sie das Ergebnis überprüfen. Fügen Sie die Zeilenumbrüche erneut entsprechend ein, wenn Sie zeilenweise voranschreiten.

71

IntelliJ hat für diesen Fall ein so nettes Plugin wie ein Java Stream Debugger Plugin. Sie sollten es überprüfen: https://plugins.jetbrains.com/plugin/9696-Java-stream-debugger?platform=hootsuite

Es erweitert das IDEA Debugger-Toolfenster um die Schaltfläche Trace Current Stream Chain (Aktuelle Stream-Kette verfolgen), die aktiviert wird, wenn der Debugger in einer Kette von Stream-API-Aufrufen angehalten wird.

Es hat eine nette Schnittstelle für die Arbeit mit separaten Streams-Operationen und gibt Ihnen die Möglichkeit, einige Werte zu befolgen, die Sie debuggen sollten.

Java Stream Debugger

Sie können es manuell über das Debug-Fenster starten, indem Sie hier klicken:

enter image description here

25

Das Debuggen von Lambdas funktioniert auch gut mit NetBeans. Ich verwende NetBeans 8 und JDK 8u5.

Wenn Sie einen Haltepunkt auf einer Linie setzen, auf der ein Lambda vorliegt, werden Sie beim Einrichten der Pipeline tatsächlich einmal und dann für jedes Stream-Element einmal getroffen. In Ihrem Beispiel ist das erste Mal, wenn Sie den Haltepunkt treffen, der Aufruf map(), mit dem die Stream-Pipeline eingerichtet wird:

first breakpoint

Sie können die Aufrufliste sowie die lokalen Variablen und Parameterwerte für main wie erwartet anzeigen. Wenn Sie fortfahren, wird der "gleiche" Haltepunkt erneut getroffen, außer diesmal im Aufruf des Lambda:

enter image description here

Beachten Sie, dass sich der Aufrufstapel dieses Mal tief in der Streams-Maschinerie befindet und die lokalen Variablen die Locals des Lambda selbst sind, nicht die einschließende main -Methode. (Ich habe die Werte in der Liste naturals geändert, um dies zu verdeutlichen.)

Wie Marlon Bernardes (+1) hervorgehoben hat, können Sie peek verwenden, um Werte zu überprüfen, während sie in der Pipeline vorbeigehen. Seien Sie jedoch vorsichtig, wenn Sie dies aus einem parallelen Stream verwenden. Die Werte können in einer unvorhersehbaren Reihenfolge über verschiedene Threads gedruckt werden. Wenn Sie Werte in einer Debugging-Datenstruktur von peek speichern, muss diese Datenstruktur natürlich threadsicher sein.

Wenn Sie häufig Lambdas (insbesondere mehrzeilige Anweisungs-Lambdas) debuggen, ist es möglicherweise vorzuziehen, das Lambda in eine benannte Methode zu extrahieren und dann mithilfe einer Methodenreferenz darauf zu verweisen. Beispielsweise,

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

Dies kann es einfacher machen, während des Debuggens zu sehen, was los ist. Das Extrahieren von Methoden auf diese Weise erleichtert außerdem den Komponententest. Wenn Ihr Lambda so kompliziert ist, dass Sie es in einem Schritt durcharbeiten müssen, möchten Sie wahrscheinlich trotzdem eine Reihe von Unit-Tests durchführen.

21
Stuart Marks

Intellij IDEA 15 scheint es noch einfacher zu machen, es erlaubt, in einem Teil der Zeile anzuhalten, wo Lambda ist, siehe die erste Funktion: http: //blog.jetbrains. de/idea/2015/06/intellij-idea-15-eap-is-open /

6
Egor