web-dev-qa-db-fra.com

Pourquoi la modification de la variable renvoyée dans un bloc finally ne modifie-t-elle pas la valeur de retour?

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?

146
Devendra

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 bloc finally est exécuté, puis il y a un choix:

  • Si le bloc finally se termine normalement, alors l'instruction try se termine brutalement pour la raison R.
  • Si le bloc finally se termine brusquement pour la raison S, alors l'instruction try 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.

166
Ted Hopp

Parce que la valeur de retour est placée sur la pile avant l'appel à finalement.

64
Tordek

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.

33
Mikhail

Il y a 2 choses à noter ici:

  • Les cordes sont immuables. Lorsque vous définissez s sur "override variable s", vous définissez s pour faire référence à la chaîne insérée, sans modifier le tampon de caractères inhérent de l'objet s pour le remplacer par "override variable s".
  • Vous mettez une référence aux s sur la pile pour revenir au code appelant. Par la suite (lorsque le bloc finalement s'exécute), la modification de la référence ne devrait rien faire pour la valeur de retour déjà sur la pile.
22
0xCAFEBABE

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
13
Frank

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.

6
Kiran Jujare

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;
}
0
Achintya Jha