Strange finally behaviour?

2019-01-07 21:38发布

问题:

public class Test2 {

    public static void main(String[] args) {
        Test2 obj=new Test2();
        String a=obj.go();

        System.out.print(a);
    }


    public String go() {
        String q="hii";
        try {
            return q;
        }
        finally {
            q="hello";
            System.out.println("finally value of q is "+q);
        }
    }

Why is this printing hii after returning from the function go(), the value has changed to "hello" in the finally block?

the output of the program is

finally value of q is hello
hii

回答1:

That's because you returned a value that was evaluated from q before you changed the value of q in the finally block. You returned q, which evaluated its value; then you changed q in the finally block, which didn't affect the loaded value; then the return completed, using the evaluated value.

Don't write tricky code like this. If it confuses the guy who wrote it, imagine the problems it will cause the next guy, a few years down the track when you are somewhere else.



回答2:

return returns value not reference. When return q; gets executed in catch current value of q reference is cached by method as its result. So even if in finally block you will reassign q with new value it doesn't affect value already cached by method.

If you want to update value which should be returned you will have to use another return in your finally block like

} finally {
    q = "hello";
    System.out.println("finally value of q is " + q);

    return q;//here you set other value in return
}

Other way of affecting returned value is by changing state of cached object. For instance if q was a List we could add new element to it (but notice that changing state is not the same as reassigning a new instance, just like we can change state of final variable, but we can't reassign it).

} finally {
    q.add(new Element); //this will place new element (update) in List 
    //object stored by return because it is same object from q reference
    System.out.println("finally value of q is " + q);
}


回答3:

Finally executes after return but before the method actually returns to the caller. This is analogous to throw. It happens after throw and before exiting the block. The return value is already set in some register by reading the variable q. If q was mutable, you could mutate it in finally and you would see that change in the caller. Why does it work this way? For one, it probably is the least complicated to implement. Two, it gives you maximal flexibility. You can override the return value in finally with an explicit return. Preserving it by default lets you choose either behavior.



回答4:

[Edited after comment from EJP, my first response did not answer question and was also wrong.]
Now my answer should be correct explaining that as the try block and the finally block completes normally q is returned. And the reason why the value "hii" is returned is explained in EJPs answer. I'm still looking for an explanation in the JLS.

Have a look at JLS 14.20.2 Execution of try-catch-finally

A try statement with a finally block is executed by first executing the try block. Then there is a choice:

If execution of the try block completes normally, then the finally block is executed, and then there is a choice:
If the finally block completes normally, then the try statement completes normally.
[...]

and JLS 14.17 The return Statement

A return statement with an Expression attempts to transfer control to the invoker of the method that contains it; the value of the Expression becomes the value of the method invocation. More precisely, execution of such a return statement first evaluates the Expression. If the evaluation of the Expression completes abruptly for some reason, then the return statement completes abruptly for that reason. If evaluation of the Expression completes normally, producing a value V, then the return statement completes abruptly, the reason being a return with value V

And:

The preceding descriptions say "attempts to transfer control" rather than just "transfers control" because if there are any try statements (§14.20) within the method or constructor whose try blocks contain the return statement, then any finally clauses of those try statements will be executed, in order, innermost to outermost, before control is transferred to the invoker of the method or constructor. Abrupt completion of a finally clause can disrupt the transfer of control initiated by a return statement.



回答5:

Try using StringBuffer instead of String and you will see the change .... it seems the return statement blocks the object which is to be returned and not the reference. You could also try to verify this by printing the hashcode of :

  • object being returned from go()
  • object in finally
  • object being printed from main()

    public static void main(String[] args){

        Test obj=new Test();
            StringBuffer a=obj.go();
            System.out.print(a);
        }
      public StringBuffer go() {
            StringBuffer q=new StringBuffer("hii");
            try {
                return q;
            }
            finally {
                q=q.append("hello");
                System.out.println("finally value of q is "+q);
            }
        }
    


回答6:

What is finally block?

-By definition from Java "The finally block always executes when the try block exits. This ensures that the finally block is executed even if an unexpected exception occurs."

So, it prints "finally value of q is hello" as soon as it exists the try block and goes to line System.out.print(a); and prints the value returned by method go().

If you have a debuggers like netbeans or eclipse, it can be analyzed by keeping the break point and waking through the code.



回答7:

Well, what I found is as follows,

Return actually returns a value and its gets copied to String a=obj.go();, before execution goes to Finally.

Lets verify it by following experiments.

public class Test2 {

   public static void main(String[] args) {
     Test2 obj=new Test2();
     String a=obj.go();

     System.out.print(a);
   } 


   public String go() {
     String q="hii";
     try {
        return q;
     }
     finally {
        q="hello";
        System.out.println("finally value of q is "+q);
     }
}

the output of the program is

finally value of q is hello

hii

and if we take StringBuffer instead of String as follows,

public class Test2 {

    public static void main(String[] args) {
        // TODO Auto-generated method stub

        Test2 obj=new Test2();
        StringBuffer a=obj.go();

        System.out.print(a);
    }


    public  StringBuffer go(){
        StringBuffer q=new StringBuffer("hii");
        try{

            return q;
        }
        finally{

            q.replace(0, q.length(), "hello");
            System.out.println("finally value of q is "+q);
            /*return q1;*/

        }

    }
}

The output comesout to be,

finally value of q is hello

hello

and finally if we take int instead of String as follows,

public class Test2 {

    public static void main(String[] args) {
        // TODO Auto-generated method stub

        Test2 obj=new Test2();
        int a=obj.go();

        System.out.print(a);
    }


    public  int go(){
        int q=1;
        try{

            return q;
        }
        finally{

            q=2;
            System.out.println("finally value of q is "+q);
            /*return q1;*/

        }

    }
}

the output is

finally value of q is 2

1

                              **Ananlysis**

1.In first case, return copied adress of String in variable a, then excecution goes to Finally where String is changed. But since in case of Strings, we can't manipulate any String a new String is constructed. So in variable a address of original string is saved, which gets printed.

2.In second case, return copied address of StringBuffer in variable a, and in finally this StringBuffer object is manipulated, rather creating new one. so the value which was stored in variable a also gets manipulated, that's seen in print statement.

3.In third case, value of int is copied in variable a, before execution goes to finally. and thus a gets value of 1. and then in finally we changed value of q which doesn't anyway change value of a.



标签: java finally