Estoy usando AdoptOpenJDK jdk81212-b04
en Ubuntu Linux, ejecutándome en Eclipse 4.13. Tengo un método en Swing que crea una lambda dentro de una lambda; ambos probablemente sean llamados en hilos separados. Se ve así (pseudocódigo):
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);
}
});
Puede ver que las lambdas tienen varias capas de profundidad y que, finalmente, la "carga útil" capturada se guarda en un archivo, más o menos.
Pero antes de considerar las capas y los hilos, vayamos directamente al problema:
- La primera vez que llamo
createAction()
, los dosSystem.out.println()
métodos imprimen exactamente el mismo código hash, lo que indica que la carga útil capturada dentro de la lambda es la misma que le pasécreateAction()
. - Si luego llamo
createAction()
con una carga útil diferente, ¡los dosSystem.out.println()
valores impresos son diferentes! ¡En particular, la segunda línea impresa siempre indica el mismo valor que se imprimió en el paso 1!
Puedo repetir esto una y otra vez; ¡la carga útil real pasada seguirá obteniendo un código hash de identidad diferente, mientras que la segunda línea impresa (dentro de la lambda) permanecerá igual! Finalmente, algo hará clic y de repente los números volverán a ser los mismos, pero luego divergirán con el mismo comportamiento.
¿De alguna manera Java está almacenando en caché la lambda, así como el argumento que va a la lambda? Pero, ¿cómo es esto posible? El payload
argumento está marcado final
, y además, ¡las capturas lambda deben ser efectivamente finales de todos modos!
- ¿Existe un error de Java 8 que no reconoce un lambda que no debe almacenarse en caché si la variable capturada tiene varias lambdas de profundidad?
- ¿Hay un error de Java 8 que está almacenando en caché lambdas y argumentos lambda a través de hilos?
- ¿O hay algo que no entiendo sobre los argumentos del método de captura lambda versus las variables locales del método?
Primer intento fallido de solución
Ayer pensé que podría evitar este comportamiento simplemente capturando el parámetro del método localmente en la pila de métodos:
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);
}
});
Con esa sola línea Data theRealPayload = payload
, si en adelante utilicé en theRealPayload
lugar de payload
repentinamente el error ya no aparecía, y cada vez que llamé createAction()
, las dos líneas impresas indican exactamente la misma instancia de la variable capturada.
Sin embargo, hoy esta solución ha dejado de funcionar.
Problema de direcciones de corrección de errores por separado; ¿Pero por qué?
Encontré un error separado que arrojaba una excepción dentro showStatusDialogWithWorker()
. Básicamente showStatusDialogWithWorker()
se supone que crea el trabajador (en la lambda aprobada) y muestra un cuadro de diálogo de estado hasta que el trabajador haya terminado. Hubo un error que crearía al trabajador correctamente, pero no pudo crear el diálogo, lanzando una excepción que surgiría y nunca quedaría atrapada. Solucioné este error para que showStatusDialogWithWorker()
muestre con éxito el cuadro de diálogo cuando el trabajador se está ejecutando y luego lo cierra una vez que el trabajador termina. Ahora ya no puedo reproducir el problema de captura lambda.
Pero, ¿por qué algo dentro se showStatusDialogWithWorker()
relaciona con el problema? Cuando imprimía System.identityHashCode()
fuera y dentro de la lambda, y los valores eran diferentes, esto sucedía antes de que showStatusDialogWithWorker()
se llamara y antes de que se lanzara la excepción. ¿Por qué una excepción posterior debería marcar la diferencia?
Además, la pregunta fundamental sigue siendo: ¿cómo es posible que un final
parámetro pasado por un método y capturado por una lambda pueda cambiar?
final
cambiar la identidad de un argumento de método fuera y dentro de la lambda? ¿Y por qué capturar la variable como una final
variable local en la pila hace alguna diferencia?
final
variable en la pila ya no soluciona el problema. Yo sigo investigando.
SwingAction
sí mismo. ¿Qué haces con la SwingAction
devolución del método?