try と catch と finally

某日記(ネタ帳?)より勝手にインスパイヤ(すみません...)。Java

try {
    throw new RuntimeException("hoge");
} catch (Exception e) {
    throw new RuntimeException("fuga");
} finally {
    throw new RuntimeException("piyo");
}

と書いたら何が投げられるだろうか。また、

try {
    return true;
} finally {
    return false;
}

あるいは

for (;;) {
    try {
        return true;
    } finally {
        break;
    }
}
return false;

で返却される値は何だろうか。
正解は順に、例外 "piyo"、false、false となる。とりあえず「finally が優先」と思えば間違いないのだが、この辺の挙動は言語仕様できちんと説明されているので、ちょっと要約してみる。まず、文の完了には「正常完了(normal completion)」と「中途完了(abrupt completion)」とがあって、後者は必ず何らかの理由を伴う [14.1]。で、catch のない try 文の完了は、次のように定義されている [14.20]。

  • try ブロックが正常完了した場合、
    • finally ブロックが正常完了すれば、try 文全体も正常完了する。
    • finally ブロックが理由 R で中途完了すれば、try 文全体も理由 R で中途完了する。
  • try ブロックが理由 R で中途完了した場合、
    • finally ブロックが正常完了すれば、try 文全体は理由 R で中途完了する。
    • finally ブロックが理由 S で中途完了すれば、理由 R は破棄され、try 文全体は理由 S で中途完了する。

break、return、throw の機能は、中途完了によって実現されている [14.15, 14.17, 14.18]。

  • 文“break;”は、「ラベルなし break」という理由で常に中途完了する。
  • 式 E が正常に評価され、値 V を生成するならば、文“return E;”は「値 V の return」という理由で常に中途完了する。
  • 式 E が正常に評価され、null でない値 V を生成するならば*1、文“throw E;”は「値 V の throw」という理由で常に中途完了する。

これらのことから、try ブロックにおける break、return、throw の効果は、finally ブロックにおけるそれらの効果によって上書きされることがわかる。
なお、try 文に catch ブロックがある場合でも話の大筋は変わらず(もちろん場合分けはもっと複雑になるけど)、要するに catch の優先度は try と finally の中間となっている。

*1:null だったら NullPointerExceptionインスタンスが勝手に作られる。