The difference from behaviour in Java is because try ... catch ... finally
is an expression in Kotlin, not a statement.
The way that a "try-expression" is evaluated, is defined as follows in the specification:
The try-expression evaluation evaluates its body; if any statement in the try body throws an exception (of type E), this exception, rather than being immediately propagated up the call stack, is checked for a matching catch block. If a catch block of this try-expression has an exception parameter of type T:>E , this catch block is evaluated immediately after the exception is thrown and the exception itself is passed inside the catch block as the corresponding parameter. [...]
If there is a finally block, it is evaluated after the evaluation of all previous try-expression blocks
The value of the try-expression is the same as the value of the last expression of the try body (if no exception was thrown) or the value of the last expression of the matching catch block (if an exception was thrown and matched). All other situations mean that an exception is going to be propagated up the call stack, and the value of the try-expression is undefined.
Note: as described, the finally block (if present) is always executed, but has no effect on the value of the try-expression.
So when throwing
is true, the try
, catch
, and finally
blocks are all executed, but the value of the try-expression is the value of the last expression in the catch block. This explains the behaviour in both the "explicit" and "implicit" cases.
In the "explicit return" case, return 2
is executed, but the method can't return there - the finally block still has to run! Then return 3
is executed, and now the method returns. Notably, the outer return
is never executed. You can delete the outer return
and start the method with try { ...
and get the same result.
In the "implicit return" case, 2
is evaluated, and no side effects happen since it is just a literal. Then the finally block runs, and 3
is evaluated, and again no side effects happen since it is just a literal. Now we have finished evaluating the whole try
-expression, and according to the spec, the value of the expression should be what we evaluated in the catch block - i.e. 2. And now we execute return 2
.
Side note: return ...
are also expressions, and they have the type of Nothing
(the subtype of every type), because they never evaluate to anything. This is why you are able to write return 1
etc as the last line in the try-expression blocks in the "explicit return" case. In that case, the try-expression actually has a type of Nothing
, and the outer return
is actually returning Nothing
. This is fine, because Nothing
is a subtype of Int
.