The try block completes with the execution of the return statement and the value of s at the time the return statement executes is the value returned by the method. The fact that the finally clause later changes the value of s (after the return statement completes) does not (at that point) change the return value.
Note that the above deals with changes to the value of s itself in the finally block, not to the object that s references. If s was a reference to a mutable object (which String is not) and the contents of the object were changed in the finally block, then those changes would be seen in the returned value.
The detailed rules for how all this operates can be found in Section 14.20.2 of the Java Language Specification. Note that execution of a return statement counts as an abrupt termination of the try block (the section starting "If execution of the try block completes abruptly for any other reason R...." applies). See Section 14.17 of the JLS for why a return statement is an abrupt termination of a block.
By way of further detail: if both the try block and the finally block of a try-finally statement terminate abruptly because of return statements, then the following rules from §14.20.2 apply:
If execution of the try block completes abruptly for any other reason R [besides throwing an exception], then the finally block is executed, and then there is a choice:
- If the finally block completes normally, then the try statement completes abruptly for reason R.
- If the finally block completes abruptly for reason S, then the try statement completes abruptly for reason S (and reason R is discarded).
The result is that the return statement in the finally block determines the return value of the entire try-finally statement, and the returned value from the try block is discarded. A similar thing occurs in a try-catch-finally statement if the try block throws an exception, it is caught by a catch block, and both the catch block and the finally block have return statements.