Wie debuggt man stream().map(…) mit Lambda-Ausdrücken?

Lesezeit: 9 Minuten

Benutzer-Avatar
Federico Piazza

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

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

Bei dieser Migration muss ich zum Beispiel den Guava-Code in Java 8-Änderungen ändern. Also, die Änderungen, die ich vornehme, sind die Art von:

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

Mit Guava war ich sehr komfortabel beim Debuggen des Codes, da ich jeden Transformationsprozess debuggen konnte, aber meine Sorge ist, wie man zum Beispiel debuggt .map(n -> n*2).

Mit dem Debugger kann ich Code sehen wie:

@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 das nicht finden n * 2 Transformation.

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

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

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

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

Dank an Stuart Marks Der Ansatz, Methodenreferenzen zu haben, ermöglichte es mir auch, den Transformationsprozess zu debuggen:

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

Dank an Marlon Bernardes Antwort Ich habe festgestellt, dass mein Eclipse nicht das zeigt, was es sollte, und die Verwendung von peek() hat geholfen, Ergebnisse anzuzeigen.

  • Sie müssen Ihre vorübergehende Beschäftigung nicht deklarieren result variabel als Integer. Eine einfache int sollte es auch tun, wenn Sie es sind mapping ein int zu einem int

    – Holger

    3. Juli 2014 um 17:05 Uhr

  • Ich füge auch hinzu, dass es den verbesserten Debugger in IntelliJ IDEA 14 gibt. Jetzt können wir die Lamdas debuggen.

    – Michail

    29. Dezember 2014 um 17:52 Uhr

Benutzer-Avatar
Marlon Bernardes

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

Debuggen von Lambdas

Ein weiterer Ansatz ist die Verwendung peek So überprüfen Sie die Elemente des Streams:

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

AKTUALISIEREN:

Ich glaube, Sie werden verwirrt, weil map ist ein intermediate operation – mit anderen Worten: es ist eine faule Operation, die erst nach a ausgeführt wird terminal operation wurde ausgeführt. Also, wenn Sie anrufen stream.map(n -> n * 2) Der Lambda-Körper wird im Moment nicht ausgeführt. Sie müssen einen Haltepunkt setzen und ihn überprüfen, nachdem eine Terminaloperation aufgerufen wurde (collectin diesem Fall).

Prüfen Stream-Operationen für weitere Erklärungen.

UPDATE 2:

Zitat von Holgers Kommentar:

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

Einfügen eines Zeilenumbruchs direkt danach map(
würde es Ihnen erlauben, nur einen Haltepunkt für den Lambda-Ausdruck zu setzen. Und es ist nicht ungewöhnlich, dass Debugger keine Zwischenwerte von a anzeigen return Aussage. Änderung des Lambda auf n -> { int result=n * 2; return result; }
würde es Ihnen ermöglichen, das Ergebnis zu überprüfen. Fügen Sie wieder Zeilenumbrüche entsprechend ein, wenn Sie Zeile für Zeile schrittweise vorgehen…

  • Danke für den Druckbildschirm. Welche Version von Eclipse haben Sie oder was haben Sie getan, um diesen Dialog zu erhalten? Ich habe versucht, mit inspect und display und bekomme n cannot be resolved to a variable. Übrigens, Peek ist auch nützlich, druckt aber alle Werte auf einmal. Ich möchte jeden Iterationsprozess sehen, um die Transformation zu überprüfen. Ist es möglich?

    – Piazza Federico

    2. Juli 2014 um 22:47 Uhr

  • Ich verwende Eclipse Kepler SR2 (mit installierter Java 8-Unterstützung vom Marktplatz von Eclipse).

    – Marlon Bernardes

    2. Juli 2014 um 22:57 Uhr

  • Benutzt du auch Eclipse? Setzen Sie einfach einen Haltepunkt auf .map Zeile und drücken Sie mehrmals F8.

    – Marlon Bernardes

    2. Juli 2014 um 22:58 Uhr

  • @Fede: Was es hier schwierig macht, ist, dass der Anruf an map und der Lambda-Ausdruck befinden sich in einer Zeile, sodass ein Zeilenhaltepunkt bei zwei völlig unabhängigen Aktionen stoppt. Einfügen eines Zeilenumbruchs direkt danach map( würde es Ihnen erlauben, nur einen Haltepunkt für den Lambda-Ausdruck zu setzen. Und es ist nicht ungewöhnlich, dass Debugger keine Zwischenwerte von a anzeigen return Aussage. Änderung des Lambda auf n -> { int result=n * 2; return result; } würde dir eine Inspektion ermöglichen result. Fügen Sie wieder Zeilenumbrüche entsprechend ein, wenn Sie Zeile für Zeile schrittweise vorgehen…

    – Holger

    3. Juli 2014 um 8:15 Uhr

  • @Marlon Bernardes: Sicher, Sie können es der Antwort hinzufügen, da dies der Zweck von Kommentaren ist: zur Verbesserung des Inhalts beizutragen. Übrigens, ich habe den zitierten Text bearbeitet und Codeformatierung hinzugefügt…

    – Holger

    3. Juli 2014 um 15:40 Uhr

Benutzer-Avatar
Dmytro Melnychuk

IntelliJ hat für diesen Fall ein so nettes Plugin wie a 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, die aktiv wird, wenn der Debugger innerhalb einer Kette von Stream-API-Aufrufen stoppt.

Es hat eine nette Schnittstelle für die Arbeit mit separaten Stream-Operationen und gibt Ihnen die Möglichkeit, einigen Werten zu folgen, die Sie debuggen sollten.

Java-Stream-Debugger

Sie können es manuell aus dem Debug-Fenster starten, indem Sie hier klicken:

Geben Sie hier die Bildbeschreibung ein

  • Schade, dass dies nicht für Optionals gilt, die bis zur gleichen Komplexität anwachsen können

    – Daniel Alder

    28. September 2020 um 10:40 Uhr

Benutzer-Avatar
Stuart Mark

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

Wenn Sie einen Haltepunkt in einer Zeile setzen, in der sich ein Lambda befindet, werden Sie tatsächlich einmal getroffen, wenn die Pipeline eingerichtet ist, und dann einmal für jedes Stream-Element. Wenn Sie Ihr Beispiel verwenden, ist das erste Mal, wenn Sie den Haltepunkt erreichen, der map() Aufruf, der die Stream-Pipeline einrichtet:

erster Haltepunkt

Sie können den Aufrufstapel und die lokalen Variablen und Parameterwerte für sehen main wie Sie es erwarten würden. Wenn Sie fortfahren, wird der “gleiche” Haltepunkt erneut erreicht, außer dass er sich diesmal innerhalb des Aufrufs an das Lambda befindet:

Geben Sie hier die Bildbeschreibung ein

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

Wie Marlon Bernardes betonte (+1), können Sie verwenden peek um Werte zu inspizieren, während sie in der Pipeline vorbeiziehen. Seien Sie jedoch vorsichtig, wenn Sie dies aus einem parallelen Stream verwenden. Die Werte können in einer unvorhersehbaren Reihenfolge über verschiedene Threads hinweg gedruckt werden. Wenn Sie Werte in einer Debugging-Datenstruktur von speichern peekmuss diese Datenstruktur natürlich Thread-sicher sein.

Wenn Sie Lambdas häufig debuggen (insbesondere Lambdas mit mehrzeiligen Anweisungen), ist es möglicherweise vorzuziehen, das Lambda in eine benannte Methode zu extrahieren und dann mit einer Methodenreferenz darauf zu verweisen. Zum Beispiel,

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 macht es möglicherweise einfacher zu sehen, was während des Debuggens vor sich geht. Darüber hinaus erleichtert das Extrahieren von Methoden auf diese Weise den Komponententest. Wenn Ihr Lambda so kompliziert ist, dass Sie es in Einzelschritten durchlaufen müssen, möchten Sie wahrscheinlich sowieso eine Reihe von Komponententests dafür haben.

  • Mein Problem war, dass ich den Lambda-Körper nicht debuggen konnte, aber Ihr Ansatz, Methodenreferenzen zu verwenden, hat mir bei dem, was ich wollte, sehr geholfen. Sie könnten Ihre Antwort mit dem Holger-Ansatz aktualisieren, der auch durch Hinzufügen perfekt funktionierte { int result=n * 2; return result; } in verschiedenen Zeilen und ich konnte die Antwort akzeptieren, da beide Antworten hilfreich waren. +1 natürlich.

    – Piazza Federico

    3. Juli 2014 um 15:19 Uhr

  • @Fede Sieht so aus, als ob die andere Antwort bereits aktualisiert wurde, sodass meine nicht aktualisiert werden muss. Ich hasse sowieso mehrzeilige Lambdas. 🙂

    – Stuart Marks

    3. Juli 2014 um 15:51 Uhr

  • @Stuart Marks: Ich bevorzuge auch einzeilige Lambdas. Daher entferne ich normalerweise die Zeilenumbrüche nach dem Debuggen ⟨was auch für andere (gewöhnliche) zusammengesetzte Anweisungen gilt⟩.

    – Holger

    3. Juli 2014 um 17:03 Uhr

  • @Fede Keine Sorge. Es ist Ihr Vorrecht als Fragesteller, die Antwort zu akzeptieren, die Sie bevorzugen. Danke für die +1.

    – Stuart Marks

    3. Juli 2014 um 17:22 Uhr

  • Ich denke, dass das Erstellen von Methodenreferenzen nicht nur Methoden einfacher für Komponententests macht, sondern auch zu einem besser lesbaren Code führt. Gute Antwort! (+1)

    – Marlon Bernardes

    3. Juli 2014 um 18:06 Uhr

Benutzer-Avatar
Federico Piazza

Nur um aktualisierte Details (Oktober 2019) bereitzustellen, hat IntelliJ eine ziemlich nette Integration hinzugefügt, um diese Art von Code zu debuggen, die äußerst nützlich ist.

Wenn wir bei einer Zeile anhalten, die ein Lambda enthält, wenn wir drücken F7 (eintreten), dann hebt IntelliJ hervor, was das zu debuggende Snippet sein wird. Wir können wechseln, mit welchem ​​Chunk debuggt werden soll Tab und sobald wir uns entschieden haben, klicken wir F7 wieder.

Hier einige Screenshots zur Veranschaulichung:

1- Drücken F7 (Eintreten) Taste, zeigt die Highlights (oder Auswahlmodus)
Geben Sie hier die Bildbeschreibung ein

2- Verwenden Tab mehrmals, um das zu debuggende Snippet auszuwählen
Geben Sie hier die Bildbeschreibung ein

3- Drücken F7 (eintreten) Schlüssel zum Eintreten
Geben Sie hier die Bildbeschreibung ein

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

Benutzer-Avatar
Sashank Samantray

Das Debuggen mit IDEs ist immer hilfreich, aber der ideale Weg zum Debuggen aller Elemente in einem Stream ist die Verwendung von peek() vor einer Terminalmethodenoperation, da Java-Steams träge ausgewertet werden. Wenn also keine Terminalmethode aufgerufen wird, wird der jeweilige Stream dies tun nicht ausgewertet werden.

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

1335140cookie-checkWie debuggt man stream().map(…) mit Lambda-Ausdrücken?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy