propagacion - todas las excepciones en java
¿Por qué el cambio de la variable devuelta en un bloque finally no cambia el valor de retorno? (7)
Cambio tu código un poco para probar el punto de Ted.
Como se puede ver en la salida s
se cambia, pero después de la devolución.
public class Test {
public String s;
public String foo() {
try {
s = "dev";
return s;
} finally {
s = "override variable s";
System.out.println("Entry in finally Block");
}
}
public static void main(String[] xyz) {
Test obj = new Test();
System.out.println(obj.foo());
System.out.println(obj.s);
}
}
Salida:
Entry in finally Block
dev
override variable s
Tengo una clase Java simple como se muestra a continuación:
public class Test {
private String s;
public String foo() {
try {
s = "dev";
return s;
}
finally {
s = "override variable s";
System.out.println("Entry in finally Block");
}
}
public static void main(String[] xyz) {
Test obj = new Test();
System.out.println(obj.foo());
}
}
Y el resultado de este código es este:
Entry in finally Block
dev
¿Por qué s
no se anula en el bloque finally
, pero controla la salida impresa?
El bloque try
se completa con la ejecución de la instrucción return
y el valor de s
en el momento en que se ejecuta la instrucción return
es el valor devuelto por el método. El hecho de que la cláusula finally
cambie más tarde el valor de s
(después de que se complete la declaración de return
) no cambia (en ese punto) el valor de retorno.
Tenga en cuenta que lo anterior se refiere a los cambios en el valor de s
en el bloque finally
, no en el objeto al que hace referencia. Si s
era una referencia a un objeto mutable (qué String
no es) y el contenido del objeto se cambió en el bloque finally
, entonces esos cambios se verían en el valor devuelto.
Las reglas detalladas de cómo funciona todo esto se pueden encontrar en la Sección 14.20.2 de la Especificación del lenguaje Java . Tenga en cuenta que la ejecución de una declaración de return
cuenta como una terminación abrupta del bloque try
(la sección que comienza " Si la ejecución del bloque try se completa abruptamente por cualquier otra razón R ... " se aplica). Consulte la Sección 14.17 del JLS para saber por qué una declaración de return
es una terminación abrupta de un bloque.
A modo de más detalle: si tanto el bloque try
como el bloque finally
de una instrucción try-finally
finalizan abruptamente debido a declaraciones return
, entonces se aplican las siguientes reglas de §14.20.2:
Si la ejecución del bloque
try
se completa abruptamente por cualquier otra razón R [además de lanzar una excepción], entonces se ejecuta el bloquefinally
, y luego hay una opción:
- Si el bloque
finally
se completa normalmente, la instruccióntry
completa abruptamente por el motivo R.- Si el bloque final se completa abruptamente por la razón S, entonces la instrucción
try
completa abruptamente por la razón S (y la razón R se descarta).
El resultado es que la declaración return
en el bloque finally
determina el valor de retorno de la sentencia try-finally
, y el valor devuelto del bloque try
se descarta. Algo similar ocurre en una declaración try-catch-finally
si el bloque try
arroja una excepción, es capturado por un bloque catch
, y tanto el catch
block como el finally
block tienen return
statements.
Hay 2 cosas dignas de mención aquí:
- Las cadenas son inmutables. Cuando establece s para "anular la variable s", establece s para hacer referencia a la cadena en línea, sin alterar el búfer de caracteres inherente del objeto s para cambiar a "anular la variable s".
- Pones una referencia a la s en la pila para regresar al código de llamada. Después (cuando se ejecuta el bloque finally), alterar la referencia no debería hacer nada para el valor de retorno que ya está en la pila.
Intente esto: si desea imprimir el valor de sobrescritura de s.
finally {
s = "override variable s";
System.out.println("Entry in finally Block");
return s;
}
Porque el valor de retorno se pone en la pila antes de la llamada para finalmente.
Si miramos dentro de bytecode, notaremos que JDK ha realizado una optimización significativa, y el método foo () se ve así:
String tmp = null;
try {
s = "dev"
tmp = s;
s = "override variable s";
return tmp;
} catch (RuntimeException e){
s = "override variable s";
throw e;
}
Y bytecode:
0: ldc #7; //loading String "dev"
2: putstatic #8; //storing it to a static variable
5: getstatic #8; //loading "dev" from a static variable
8: astore_0 //storing "dev" to a temp variable
9: ldc #9; //loading String "override variable s"
11: putstatic #8; //setting a static variable
14: aload_0 //loading a temp avariable
15: areturn //returning it
16: astore_1
17: ldc #9; //loading String "override variable s"
19: putstatic #8; //setting a static variable
22: aload_1
23: athrow
java preservado "dev" cadena de ser cambiado antes de regresar. De hecho, aquí no hay un bloqueo definitivo.
Técnicamente hablando, el return
en el bloque try no se ignorará si se define un bloque finally
, solo si ese bloque finally también incluye un return
.
Es una decisión de diseño dudosa que probablemente fue un error en retrospectiva (al igual que las referencias que pueden ser anulables / mutables por defecto y, según algunos, las excepciones marcadas). En muchos sentidos, este comportamiento es exactamente coherente con la comprensión coloquial de lo que finally
significa: "no importa lo que ocurra de antemano en el bloque try
, siempre ejecute este código". Por lo tanto, si devuelve true desde un bloque finally
, el efecto general siempre debe ser return s
, ¿no?
En general, esto rara vez es una buena expresión idiomática, y debería usar finally
bloques generosamente para limpiar / cerrar recursos, pero rara vez devuelve un valor de ellos.