Wie debuggt man stream().map(…) mit Lambda-Ausdrücken?
Lesezeit: 9 Minuten
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
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).
Ein weiterer Ansatz ist die Verwendung peek So überprüfen Sie die Elemente des Streams:
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).
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…
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.
Sie können es manuell aus dem Debug-Fenster starten, indem Sie hier klicken:
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
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:
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:
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
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)
2- Verwenden Tab mehrmals, um das zu debuggende Snippet auszuwählen
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.
Sie müssen Ihre vorübergehende Beschäftigung nicht deklarieren
result
variabel alsInteger
. Eine einfacheint
sollte es auch tun, wenn Sie es sindmap
ping einint
zu einemint
…– 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