Ich verwende AdoptOpenJDK jdk81212-b04
unter Ubuntu Linux unter Eclipse 4.13. Ich habe eine Methode in Swing, die ein Lambda in einem Lambda erzeugt. beide werden wahrscheinlich auf separaten Threads aufgerufen. Es sieht so aus (Pseudocode):
private SwingAction createAction(final Data payload) {
System.out.println(System.identityHashCode(payload));
return new SwingAction(() -> {
System.out.println(System.identityHashCode(payload));
//do stuff
//show an "are you sure?" modal dialog and get a response
//show a file selection dialog
//when the dialog completes, create a worker and show a status:
showStatusDialogWithWorker(() -> new SwingWorker() {
protected String doInBackground() {
save(payload);
}
});
Sie können sehen, dass die Lambdas mehrere Schichten tief sind und dass die "Nutzlast", die erfasst wurde, schließlich mehr oder weniger in einer Datei gespeichert wird.
Bevor wir jedoch die Ebenen und Threads betrachten, gehen wir direkt zum Problem:
- Beim ersten Aufruf drucken
createAction()
die beidenSystem.out.println()
Methoden genau denselben Hash-Code, was darauf hinweist, dass die erfasste Nutzlast im Lambda dieselbe ist, an die ich übergeben habecreateAction()
. - Wenn ich später
createAction()
mit einer anderen Nutzlast anrufe, sind die beidenSystem.out.println()
gedruckten Werte unterschiedlich! Insbesondere zeigt die zweite gedruckte Zeile immer den gleichen Wert an, der in Schritt 1 gedruckt wurde !!
Ich kann das immer und immer wieder wiederholen; Die tatsächlich übergebene Nutzlast erhält immer wieder einen anderen Identitäts-Hash-Code, während die zweite gedruckte Zeile (innerhalb des Lambda) gleich bleibt! Irgendwann wird etwas klicken und plötzlich werden die Zahlen wieder gleich sein, aber dann werden sie mit dem gleichen Verhalten auseinander gehen.
Zwischenspeichert Java irgendwie das Lambda sowie das Argument, das zum Lambda geht? Aber wie ist das möglich? Das payload
Argument ist markiert final
, und außerdem müssen Lambda-Erfassungen ohnehin endgültig sein!
- Gibt es einen Java 8-Fehler, der nicht erkennt, dass ein Lambda nicht zwischengespeichert werden sollte, wenn die erfasste Variable mehrere Lambdas tief ist?
- Gibt es einen Java 8-Fehler, der Lambdas und Lambda-Argumente über Threads hinweg zwischenspeichert?
- Oder gibt es etwas, das ich über Argumente der Lambda-Erfassungsmethode im Vergleich zu lokalen Variablen der Methode nicht verstehe?
Erster fehlgeschlagener Workaround-Versuch
Gestern dachte ich, ich könnte dieses Verhalten verhindern, indem ich den Methodenparameter lokal auf dem Methodenstapel erfasse:
private SwingAction createAction(final Data payload) {
final Data theRealPayload = payload;
System.out.println(System.identityHashCode(theRealPayload));
return new SwingAction(() -> {
System.out.println(System.identityHashCode(theRealPayload));
//do stuff
//show an "are you sure?" modal dialog and get a response
//show a file selection dialog
//when the dialog completes, create a worker and show a status:
showStatusDialogWithWorker(() -> new SwingWorker() {
protected String doInBackground() {
save(theRealPayload);
}
});
Data theRealPayload = payload
Wenn ich bei dieser einzelnen Zeile fortan theRealPayload
statt payload
plötzlich den Fehler verwendete, trat er nicht mehr auf, und jedes Mal , wenn ich anrief createAction()
, zeigen die beiden gedruckten Zeilen genau dieselbe Instanz der erfassten Variablen an.
Heute funktioniert diese Problemumgehung jedoch nicht mehr.
Separate Fehlerbehebungsadressen Problem; Aber warum?
Ich habe einen separaten Fehler gefunden, der eine Ausnahme auslöste showStatusDialogWithWorker()
. Grundsätzlich showStatusDialogWithWorker()
soll der Worker (im übergebenen Lambda) erstellt und ein Statusdialogfeld angezeigt werden, bis der Worker fertig ist. Es gab einen Fehler, durch den der Worker korrekt erstellt wurde, der Dialog jedoch nicht erstellt wurde. Dabei wurde eine Ausnahme ausgelöst, die in die Luft sprudelte und niemals abgefangen wurde. Ich habe diesen Fehler behoben, sodass showStatusDialogWithWorker()
der Dialog erfolgreich angezeigt wird, wenn der Worker ausgeführt wird, und ihn dann schließt, nachdem der Worker fertig ist. Ich kann das Lambda-Capture-Problem jetzt nicht mehr reproduzieren.
Aber warum bezieht sich etwas im Inneren überhaupt showStatusDialogWithWorker()
auf das Problem? Als ich System.identityHashCode()
außerhalb und innerhalb des Lambda druckte und die Werte unterschiedlich waren, geschah dies, bevor showStatusDialogWithWorker()
aufgerufen wurde und bevor die Ausnahme ausgelöst wurde. Warum sollte eine spätere Ausnahme einen Unterschied machen?
Außerdem bleibt die grundlegende Frage: Wie ist es überhaupt möglich, dass sich ein final
Parameter, der von einer Methode übergeben und von einem Lambda erfasst wird, jemals ändern kann?
final
Methodenarguments außerhalb und innerhalb des Lambda ändern? Und warum sollte es final
einen Unterschied machen , die Variable als lokale Variable auf dem Stapel zu erfassen ?
final
Variable auf dem Stapel behebt das Problem nicht mehr. Ich untersuche weiter.
SwingAction
selbst. Was machen Sie mit der SwingAction
von der Methode zurückgegebenen?