JAvaにおけるtry、catch、finallyに関する詳細分析


あるブロガーの文章を見て、javaの中でtry、catch、finallyの中でいくつかの問題について説明しました.
次に例(例1)を見てjavaにおけるtry,catch,finallyの処理の流れを説明する
public class TryCatchFinally {
 
    @SuppressWarnings("finally")
    public static final String test() {
        String t = "";
 
        try {
            t = "try";
            return t;
        } catch (Exception e) {
            // result = "catch";
            t = "catch";
            return t;
        } finally {
            t = "finally";
        }
    }
 
    public static void main(String[] args) {
        System.out.print(TryCatchFinally.test());
    }
 
}

まずプログラムはtry文ブロックを実行し、変数tをtryに割り当て、異常が発見されなかったため、次にfinally文ブロックを実行し、変数tをfinallyに割り当て、それからreturn t、tの値はfinallyであり、最後のtの値はfinallyであり、プログラム結果はfinallyを表示すべきであるが、実際の結果はtryである.なぜそうなるのか、まずこのコードがコンパイルされたclass対応のバイトコードを見て、仮想マシン内部がどのように実行されているかを見てみましょう.
ターゲットファイル(.classファイル)のバイトコード情報をjavap-verbose TryCatchFinallyで表示します
システム運転環境:mac os lionシステム64 bit
jdk情報:Java(TM)SE Runtime Environment(build 1.6.0_29-b 11-402-11 M 3527) Java HotSpot(TM) 64-Bit Server VM (build 20.4-b02-402, mixed mode)
コンパイルされたバイトコードの一部の情報は、testメソッドだけを見て、他のものは無視します.
public static final java.lang.String test();
  Code:
   Stack=1, Locals=4, Args_size=0
   0:    ldc    #16; //String 
   2:    astore_0
   3:    ldc    #18; //String try
   5:    astore_0
   6:    aload_0
   7:    astore_3
   8:    ldc    #20; //String finally
   10:    astore_0
   11:    aload_3
   12:    areturn
   13:    astore_1
   14:    ldc    #22; //String catch
   16:    astore_0
   17:    aload_0
   18:    astore_3
   19:    ldc    #20; //String finally
   21:    astore_0
   22:    aload_3
   23:    areturn
   24:    astore_2
   25:    ldc    #20; //String finally
   27:    astore_0
   28:    aload_2
   29:    athrow
  Exception table:
   from   to  target type
    8    13   Class java/lang/Exception
    8    24   any
   19    24   any
  LineNumberTable: 
   line 5: 0
   line 8: 3
   line 9: 6
   line 15: 8
   line 9: 11
   line 10: 13
   line 12: 14
   line 13: 17
   line 15: 19
   line 13: 22
   line 14: 24
   line 15: 25
   line 16: 28

  LocalVariableTable: 
   Start  Length  Slot  Name   Signature
     27      0    t       Ljava/lang/String;
     10      1    e       Ljava/lang/Exception;

  StackMapTable: number_of_entries = 2
   frame_type = 255 /* full_frame */
     offset_delta = 13
     locals = [ class java/lang/String ]
     stack = [ class java/lang/Exception ]
   frame_type = 74 /* same_locals_1_stack_item */
     stack = [ class java/lang/Throwable ]

まずLocalVariableTable情報を見て、この中で2つの変数を定義しました.1つはt Stringタイプで、1つはe Exceptionタイプです.
次はコード部分を見て
[0-2]行目は、0番目の変数に「」、すなわちString t=「」;
[3-6]行目、すなわちtry文ブロック付与文、すなわちt=「try」を実行する.
7行目、ポイントは7行目で、s番目に対応する値「try」を3番目の変数に払いますが、この中の3番目の変数は定義されていません.これはおかしいです.
行目[8-10]は、0番目の変数、すなわちt=「finally」
[11-12]行目、3番目の変数に対応する値を返す
バイトコードにより、try文のreturnブロックでは、returnが返す参照変数(tは参照タイプ)はtry文外で定義された参照変数tではなく、システムが参照tに対応する値、すなわちtryを指し、finally文で参照tが値finallyを指しても、returnの戻り参照はtではないので、tを参照する対応する値はtry文の戻り値とは無関係です.
 
次に例を示す:(例2)
public class TryCatchFinally {

    @SuppressWarnings("finally")
    public static final String test() {
        String t = "";

        try {
            t = "try";
            return t;
        } catch (Exception e) {
            // result = "catch";
            t = "catch";
            return t;
        } finally {
            t = "finally";
            return t;
        }
    }

    public static void main(String[] args) {
        System.out.print(TryCatchFinally.test());
    }

}

ここでは最初のコードを少し修正しましたが、finally文ブロックにreturn tの式を加えただけです.
最初のセグメントコードの解釈に従って、try{}文を先に行い、returnの前に現在のtの値tryを変数t'に保存し、finally文ブロックを実行し、変数tの値を変更し、変数tを返す.
この中には2つのreturn文がありますが、プログラムはtryかfinallyを返します.次にバイトコード情報を見てみましょう
public static final java.lang.String test();
  Code:
   Stack=1, Locals=2, Args_size=0
   0:    ldc    #16; //String 
   2:    astore_0
   3:    ldc    #18; //String try
   5:    astore_0
   6:    goto    17
   9:    astore_1
   10:    ldc    #20; //String catch
   12:    astore_0
   13:    goto    17
   16:    pop
   17:    ldc    #22; //String finally
   19:    astore_0
   20:    aload_0
   21:    areturn
  Exception table:
   from   to  target type
    9     9   Class java/lang/Exception
   16    16   any
  LineNumberTable: 
   line 5: 0
   line 8: 3
   line 9: 6
   line 10: 9
   line 12: 10
   line 13: 13
   line 14: 16
   line 15: 17
   line 16: 20

  LocalVariableTable: 
   Start  Length  Slot  Name   Signature
     19      0    t       Ljava/lang/String;
     6      1    e       Ljava/lang/Exception;

  StackMapTable: number_of_entries = 3
   frame_type = 255 /* full_frame */
     offset_delta = 9
     locals = [ class java/lang/String ]
     stack = [ class java/lang/Exception ]
   frame_type = 70 /* same_locals_1_stack_item */
     stack = [ class java/lang/Throwable ]
   frame_type = 0 /* same */

このコードが翻訳されたバイトコードは、最初のコードとはまったく異なり、code属性を見続けます.
[0-2]行、[3-5]行の第1セグメントのコード論理は類似しており、tを初期化し、try中のtをtryに付与する
6行目、ここで17行目にジャンプし、[17-19]はfinallyの付与文を実行し、変数tをfinallyに付与し、tに対応する値を返す
try文のreturn文が無視されることが分かった.jvmは、1つのメソッドに2つのreturn文があるのはあまり意味がないと考えているかもしれませんが、tryのreturn文は無視され、直接機能しているのはfinallyのreturn文なので、今回はfinallyを返します.
 
次に複雑な例を見てみましょう.(例3)
public class TryCatchFinally {

    @SuppressWarnings("finally")
    public static final String test() {
        String t = "";

        try {
            t = "try";
            Integer.parseInt(null);
            return t;
        } catch (Exception e) {
            t = "catch";
            return t;
        } finally {
            t = "finally";
            // System.out.println(t);
            // return t;
        }
    }

    public static void main(String[] args) {
        System.out.print(TryCatchFinally.test());
    }

}

この中にtry文が投げ出されます JAva.lang.NumberFormatExceptionなので、プログラムはまずcatch文の中のロジックを実行し、tはcatchに割り当てられ、returnを実行する前に、戻り値を一時変数の中のt'に保存し、finallyのロジックを実行し、tはfinallyに割り当てられますが、戻り値とt'なので、変数tの値と戻り値はもう関係ありません.戻り値はcatchです
 
例4:
public class TryCatchFinally {

    @SuppressWarnings("finally")
    public static final String test() {
        String t = "";

        try {
            t = "try";
            Integer.parseInt(null);
            return t;
        } catch (Exception e) {
            t = "catch";
            return t;
        } finally {
            t = "finally";
            return t;
        }
    }

    public static void main(String[] args) {
        System.out.print(TryCatchFinally.test());
    }

}

これは例2と少し似ていますが、try文に異常が投げ出されたため、プログラムはcatch文ブロックに移行し、catch文はreturn文を実行する前にfinallyを実行し、finally文にreturnがあればfinallyの文値を直接実行し、finallyを返します
 
例5:
public class TryCatchFinally {

    @SuppressWarnings("finally")
    public static final String test() {
        String t = "";

        try {
            t = "try";
            Integer.parseInt(null);
            return t;
        } catch (Exception e) {
            t = "catch";
            Integer.parseInt(null);
            return t;
        } finally {
            t = "finally";
            //return t;
        }
    }

    public static void main(String[] args) {
        System.out.print(TryCatchFinally.test());
    }

}

この例ではcatch文ブロックにInteger.parser(null)文を追加し,強制的に例外を投げ出した.そしてfinally文ブロックにはreturn文はありません.引き続き分析すると、try文が異常を投げ出すため、プログラムはcatch文ブロックに入り、catch文ブロックはまた異常を投げ出し、catch文が終了することを説明するとfinally文ブロックを実行し、tに値を付与する.そしてcatch文ブロックに異常が投げ出されます.結果としてjava.lang.NumberFormatException異常を放出
例6:
public class TryCatchFinally {

    @SuppressWarnings("finally")
    public static final String test() {
        String t = "";

        try {
            t = "try";
            Integer.parseInt(null);
            return t;
        } catch (Exception e) {
            t = "catch";
            Integer.parseInt(null);
            return t;
        } finally {
            t = "finally";
            return t;
        }
    }

    public static void main(String[] args) {
        System.out.print(TryCatchFinally.test());
    }

}

この例と上記の例で唯一異なるのは、finally文にreturn文ブロックがあることです.try catchで実行される論理は、上記の例と同様に、catch文ブロックに異常が投げ込まれた後、finally文に入るのが速く、tを返します.プログラムはcatch文ブロックから投げ出された異常情報を無視し,tに対応する値であるfinallyを直接返す.方法は異常を投げ出さない
例7:
public class TryCatchFinally {

    @SuppressWarnings("finally")
    public static final String test() {
        String t = "";

        try {
            t = "try";
            Integer.parseInt(null);
            return t;
        } catch (NullPointerException e) {
            t = "catch";
            return t;
        } finally {
            t = "finally";
        }
    }

    public static void main(String[] args) {
        System.out.print(TryCatchFinally.test());
    }

}

この例では、catch文のcatchはjava.lang.NumberFormatException異常ではなくNPE異常なので、catch文ブロックには入らず、finally文ブロックに直接入り、finallyがsに値を付けた後、try文からjava.lang.NumberFormatException異常を投げ出す.
例8
public class TryCatchFinally {

    @SuppressWarnings("finally")
    public static final String test() {
        String t = "";

        try {
            t = "try";
            Integer.parseInt(null);
            return t;
        } catch (NullPointerException e) {
            t = "catch";
            return t;
        } finally {
            t = "finally";
            return t;
        }
    }

    public static void main(String[] args) {
        System.out.print(TryCatchFinally.test());
    }

}

上記の例のtry catchの論理と同様に、try文はfinally文の実行を完了し、finallyはsを割り当て、sを返し、最後にプログラム結果はfinallyを返す
 
例9:
public class TryCatchFinally {

    @SuppressWarnings("finally")
    public static final String test() {
        String t = "";

        try {
            t = "try";return t;
        } catch (Exception e) {
            t = "catch";
            return t;
        } finally {
            t = "finally";
            String.valueOf(null);
            return t;
        }
    }

    public static void main(String[] args) {
        System.out.print(TryCatchFinally.test());
    }

}

この例ではfinally文にString.valueOf(null)を追加し,NPE異常を強制放出する.まずプログラムはtry文を実行し,戻り実行ではfinally文ブロックを実行し,finally文はNPE異常を放出し,結果全体はNPE異常を返す.
 
以上のすべての例をまとめる
1 try、catch、finally文では、try文にreturn文がある場合、返された後の現在のtryでの変数に対応する値が返され、その後変数を変更してもtryでのreturnの戻り値には影響しません.
2 finallyブロックにreturn文がある場合、tryまたはcatchの戻り文は無視されます.
3 finallyブロックから例外が投げ出された場合、try、catch、finallyブロック全体に例外が投げ出される
 
したがってtry,catch,finally文ブロックで注意すべきは
1 tryまたはcatchでreturn文をできるだけ使用します.finallyブロックでtryまたはcatchの戻り値を変更することはできません.
2 finallyブロックでreturn文の使用を避ける.finallyブロックでreturn文を使用すると、try、catchブロックの異常情報が消化され、エラーの発生が遮断されるからだ.
3 finallyブロックで再び例外を放出しないでください.そうしないと、try文ブロックを含むメソッド全体が例外を放出し、try、catchブロックの例外を消化します.