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 unafinal
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 laSwingAction
devolución del método?Respuestas:
No lo es. Como ha señalado, a menos que haya un error en la JVM, esto no puede suceder.
Esto es muy difícil de precisar sin un ejemplo reproducible mínimo. Aquí están las observaciones que ha realizado:
Una posible explicación que se ajusta a la evidencia es que la lambda que se llama la segunda vez es, de hecho, la lambda de la primera ejecución, y la lambda creada por la segunda ejecución se ha descartado. Eso daría las observaciones anteriores y colocaría el error dentro del código que no ha mostrado aquí.
Quizás podría agregar un registro adicional para registrar: a) los identificadores de cualquier lambda creado dentro de createAction en el momento de la creación (creo que necesitaría cambiar las lambdas en clases anon que implementen las interfaces de devolución de llamada con el registro en sus constructores) b) el ID de las lambdas en el momento de su invocación
Creo que el registro anterior sería suficiente para probar o refutar mi teoría.
GL!
fuente
No encuentro nada malo con las lambdas capturadas en su código.
Su solución alternativa no cambia la captura local, ya que acaba de declarar una nueva variable y asignarla a la misma referencia.
El problema es más probable en su manejo del
SwingAction
objeto creado. No me sorprendería si encuentra que imprimir el IdentityHashCode de ese objeto devuelto donde lo está utilizando arroja valores consistentes con sus cargas útiles. O en otras palabras, puede estar utilizando una referencia previa aSwingAction
.Esto no debería ser posible en el nivel de referencia, la variable no se puede reasignar. Sin embargo, la referencia pasada puede ser mutable, pero eso no se aplica a este caso.
fuente