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

146

J'ai une classe Java simple 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 n'est-il spas remplacé dans le finallybloc, tout en contrôlant la sortie imprimée?

Dev
la source
11
vous devriez mettre l'instruction return sur le bloc finally, répétez avec moi, le bloc finally est toujours exécuté
Juan Antonio Gomez Moriano
1
dans try block it return s
Devendra
6
L'ordre des déclarations compte. Vous revenez savant de modifier sa valeur.
Peter Lawrey
6
Mais, vous pouvez renvoyer la nouvelle valeur dans le finallybloc , contrairement à C # (ce que vous ne pouvez pas)
Alvin Wong
3
En relation: le retour "arrive-t-il après" enfin
Alvin Wong

Réponses:

167

Le trybloc se termine par l'exécution de l' returninstruction et la valeur de sau moment où l' returninstruction s'exécute est la valeur renvoyée par la méthode. Le fait que la finallyclause modifie ultérieurement la valeur de s(une fois l' returninstruction terminée) ne modifie pas (à ce stade) la valeur de retour.

Notez que ce qui précède traite des modifications de la valeur de slui - même dans le finallybloc, pas de l'objet qui fait sréférence. Si sétait une référence à un objet mutable (ce qui Stringne l'est pas) et que le contenu de l'objet était modifié dans le finallybloc, alors ces modifications seraient vues dans la valeur renvoyée.

Les règles détaillées sur la façon dont tout cela fonctionne se trouvent dans la section 14.20.2 de la spécification du langage Java . Notez que l'exécution d'une returninstruction compte comme une fin brusque du trybloc (la section commençant " Si l'exécution du bloc try se termine brusquement pour toute autre raison R .... " s'applique). Voir la section 14.17 du JLS pour savoir pourquoi une returninstruction est une fin brusque d'un bloc.

À titre de détail supplémentaire: si le trybloc et le finallybloc d'une try-finallyinstruction se terminent brusquement à cause d' returninstructions, les règles suivantes du §14.20.2 s'appliquent:

Si l'exécution du trybloc se termine brusquement pour toute autre raison R [en plus de lancer une exception], alors le finallybloc est exécuté, puis il y a un choix:

  • Si le finallybloc se termine normalement, l' tryinstruction se termine brusquement pour la raison R.
  • Si le finallybloc se termine brusquement pour la raison S, alors l' tryinstruction se termine brusquement pour la raison S (et la raison R est rejetée).

Le résultat est que l' returninstruction dans le finallybloc détermine la valeur de retour de l' try-finallyinstruction entière et la valeur renvoyée du trybloc est ignorée. Une chose similaire se produit dans une try-catch-finallyinstruction si le trybloc lève une exception, il est intercepté par un catchbloc et le catchbloc et le finallybloc ont des returninstructions.

Ted Hopp
la source
Si j'utilise la classe StringBuilder au lieu de String plutôt que d'ajouter une valeur dans le bloc finally, cela change la valeur de retour.
Devendra
6
@dev - J'en discute dans le deuxième paragraphe de ma réponse. Dans la situation que vous décrivez, le finallybloc ne modifie pas l'objet retourné (le StringBuilder) mais il peut modifier les éléments internes de l'objet. Le finallybloc s'exécute avant que la méthode ne retourne réellement (même si l' returninstruction est terminée), donc ces modifications se produisent avant que le code appelant ne voie la valeur retournée.
Ted Hopp
essayez avec List, vous obtenez le même comportement que StringBuilder.
Yogesh Prajapati
1
@yogeshprajapati - Ouais. La même chose est vraie avec une valeur de retour mutable ( StringBuilder, List, Set, ad nauseam): si vous modifiez le contenu dans le finallybloc, alors ces changements sont visibles dans le code d' appel lorsque la méthode enfin des sorties.
Ted Hopp
Cela dit ce qui se passe, cela ne dit pas pourquoi (ce serait particulièrement utile s'il indiquait où cela était couvert dans le JLS).
TJ Crowder
65

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

Tordek
la source
3
C'est vrai, mais cela ne répond pas à la question de l'OP de savoir pourquoi la chaîne retournée n'a pas changé. Cela a à voir avec l'immuabilité des chaînes et les références par rapport aux objets plus que de pousser la valeur de retour sur la pile.
templatetypedef
Les deux problèmes sont liés.
Tordek
2
@templatetypedef même si String était mutable, l'utilisation =ne le muterait pas.
Owen
1
@Saul - Le point d'Owen (ce qui est correct) était que l'immuabilité n'a rien à voir avec la raison pour laquelle le finallybloc d'OP n'a pas affecté la valeur de retour. Je pense que ce que templatetypedef aurait pu atteindre (bien que ce ne soit pas clair) est que, parce que la valeur retournée est une référence à un objet immuable, même la modification du code dans le finallybloc (autre que l'utilisation d'une autre returninstruction) ne pourrait pas affecter le valeur renvoyée par la méthode.
Ted Hopp
1
@templatetypedef Non, ce n'est pas le cas. Rien à voir avec l'immuabilité de String. La même chose se produirait avec n'importe quel autre type. Cela a à voir avec l'évaluation de l'expression de retour avant d'entrer dans le bloc finally, en d'autres termes exacfly «poussant la valeur de retour sur la pile».
Marquis of Lorne
32

Si nous regardons à l'intérieur du bytecode, nous remarquerons que JDK a fait une optimisation significative et que 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 finalement aucun blocage du tout.

Mikhail
la source
Ce n'est pas une optimisation. Il s'agit simplement d'une implémentation de la sémantique requise.
Marquis of Lorne
22

Il y a 2 choses à noter ici:

  • Les chaînes sont immuables. Lorsque vous définissez s sur "override variable s", vous définissez s pour qu'il fasse référence à la chaîne en ligne, 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 au s sur la pile pour revenir au code appelant. Ensuite (lorsque le bloc finally s'exécute), la modification de la référence ne devrait rien faire pour la valeur de retour déjà sur la pile.
0xCAFEBABE
la source
1
donc si je prends stringbuffer que sting sera remplacé ??
Devendra
10
@dev - Si vous deviez modifier le contenu du tampon dans la finallyclause, cela serait vu dans le code appelant. Toutefois, si vous attribuez un nouveau tampon de chaîne à s, le comportement serait le même que maintenant.
Ted Hopp
oui si j'ajoute dans le tampon de chaîne dans les changements de bloc finalement reflètent sur la sortie.
Devendra
@ 0xCAFEBABE vous donnez également de très bonnes réponses et de très bons concepts, je vous en remercie vivement.
Devendra
13

Je change un peu votre code pour prouver le point de Ted.

Comme vous pouvez le voir, la sortie sest en effet modifiée 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);
}
}

Production:

Entry in finally Block 
dev 
override variable s
Franc
la source
pourquoi la chaîne remplacée n'est pas renvoyée.
Devendra
2
Comme Ted et Tordek l'ont déjà dit: "la valeur de retour est mise sur la pile avant que le finally ne soit exécuté"
Frank
1
Bien que ce soit une bonne information supplémentaire, je suis réticent à la voter, car elle ne répond pas (à elle seule) à la question.
Joachim Sauer
5

Techniquement parlant, le returndans le bloc try ne sera pas ignoré si un finallybloc est défini, seulement si ce bloc finally inclut également un return.

C'est une décision de conception douteuse qui était probablement une erreur rétrospectivement (un peu comme les références pouvant être annulées / mutables par défaut et, selon certains, vérifiées). À bien des égards, ce comportement est exactement cohérent avec la compréhension familière de ce que finallysignifie - «peu importe ce qui se passe au préalable dans le trybloc, exécutez toujours ce code». Par conséquent, si vous retournez vrai à partir d'un finallybloc, l'effet global doit toujours être à return s, non?

En général, c'est rarement un bon idiome, et vous devriez utiliser finallygénéreusement des blocs pour nettoyer / fermer les ressources, mais rarement, voire jamais, en renvoyer une valeur.

Kiran Jujare
la source
Il est douteux pourquoi? Il est conforme à l'ordre d'évaluation dans tous les autres contextes.
Marquis of Lorne
0

Essayez ceci: Si vous souhaitez imprimer la valeur de remplacement de s.

finally {
    s = "override variable s";    
    System.out.println("Entry in finally Block");
    return s;
}
Achintya Jha
la source
1
il donne un avertissement .. enfin le blocage ne se termine pas normalement
Devendra