J'ai une simple classe Java comme indiqué ci-dessous:
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());
}
}
Et la sortie de ce code est la suivante:
Entry in finally Block
dev
Pourquoi s
n'est-il pas remplacé dans le bloc finally
, alors qu'il contrôle la sortie imprimée?
Le bloc try
se termine avec l'exécution de l'instruction return
et la valeur de s
au moment où l'instruction return
s'exécute est la valeur renvoyée par la méthode . Le fait que la clause finally
modifie ultérieurement la valeur de s
(une fois l'instruction return
terminée) ne change pas (à ce stade) la valeur de retour.
Notez que ce qui précède concerne les modifications apportées à la valeur de s
elle-même dans le bloc finally
, et non à l'objet référencé par s
. Si s
était une référence à un objet modifiable (ce que String
n'est pas) et que le contenu de l'objet a été modifié dans le finally
bloc, alors ces changements seraient vus dans la valeur retournée.
Les règles détaillées sur la façon dont tout cela fonctionne peuvent être trouvées dans Section 14.20.2 de la Java . Notez que l'exécution d'une instruction return
compte comme une interruption brutale du bloc try
(la section commençant " Si l'exécution du bloc try se termine brusquement pour toute autre raison R ...." s'applique). Voir Section 14.17 du JLS pour savoir pourquoi une instruction return
est une interruption brutale d'un bloc.
Pour plus de détails: si le bloc try
et le bloc finally
d'un try-finally
l'instruction se termine brutalement à cause des instructions return
, alors les règles suivantes du §14.20.2 s'appliquent:
Si l'exécution du bloc
try
se termine brutalement pour toute autre raison R [en plus de lancer une exception], alors le blocfinally
est exécuté, puis il y a un choix:
- Si le bloc
finally
se termine normalement, alors l'instructiontry
se termine brutalement pour la raison R.- Si le bloc
finally
se termine brusquement pour la raison S, alors l'instructiontry
se termine brusquement pour la raison S (et la raison R est ignorée).
Le résultat est que l'instruction return
dans le bloc finally
détermine la valeur de retour de l'ensemble try-finally
et la valeur renvoyée par le bloc try
est supprimée. Une chose similaire se produit dans un try-catch-finally
instruction si le bloc try
lève une exception, elle est interceptée par un bloc catch
, et le bloc catch
et le bloc finally
ont return
instructions.
Parce que la valeur de retour est placée sur la pile avant l'appel à finalement.
Si nous regardons à l'intérieur du bytecode, nous remarquerons que JDK a fait une optimisation significative, et la méthode foo () ressemble à:
String tmp = null;
try {
s = "dev"
tmp = s;
s = "override variable s";
return tmp;
} catch (RuntimeException e){
s = "override variable s";
throw e;
}
Et 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 a préservé la chaîne "dev" d'être modifiée avant de revenir. En fait, il n'y a pas du tout de blocage final.
Il y a 2 choses à noter ici:
Je change un peu votre code pour prouver l'intérêt de Ted.
Comme vous pouvez le voir dans la sortie s
est bien changé mais après le retour.
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);
}
}
Sortie:
Entry in finally Block
dev
override variable s
Techniquement parlant, le return
dans le bloc try ne sera pas ignoré si un bloc finally
est défini, seulement si ce dernier bloc comprend également un return
.
C'est une décision de conception douteuse qui était probablement une erreur rétrospective (un peu comme les références étant annulables/mutables par défaut, et, selon certains, vérifiées exceptions). À bien des égards, ce comportement est exactement cohérent avec la compréhension familière de ce que signifie finally
- "quoi qu'il arrive au préalable dans le bloc try
, exécutez toujours ce code." Par conséquent, si vous retournez true à partir d'un bloc finally
, l'effet global doit toujours être de return s
, non?
En général, c'est rarement un bon idiome, et vous devez utiliser les blocs finally
généreusement pour nettoyer/fermer les ressources mais rarement, voire jamais, en renvoyer une valeur.
Essayez ceci: Si vous souhaitez imprimer la valeur de substitution de s.
finally {
s = "override variable s";
System.out.println("Entry in finally Block");
return s;
}