finallyは必ずしも実行されるわけではありません

5977 ワード

ネットで見た投稿は、基本的にはfinallyが必ず実行するということで、最初は私もずっとそう思っていました.しかし、私はこの内容をテストして、いくつかの文章を見て、発見しました. finallyは必ず実行するわけではありません!
 
1、結論
finallyは必ずしも実行されるとは限らず、finallyに対応するtryコードブロックが実行される場合にのみfinallyが実行されます!!
 
2、実例と分析
2,1 finallyが実行されていない場合
次の例のコードにはfinallyコードブロックがありますが、結果としてfinallyのコードは実行されず、内容は印刷されません.
    public static int test() {
        int i = 1;
        System.out.println("test");
        //         
        i = i / 0;

        try {
            //   i = i / 0    try ,finally    
            // i = i / 0;
            System.out.println("try code");
        } finally {
            System.out.println("finally code");
        }
    }
上記のようにtryコードブロックの前に異常が発生しました.その後のコードは実行されず、finallyは自然に実行されません.最初の結論では、finallyは必ずしも実行されるとは限らず、finallyに対応するtryコードブロックが実行された場合にのみfinallyが実行される!!コード内の異常はtryコードの前に発生し、tryコードブロックが実行されず、最終的にfinallyも実行されない.
 
2,2 finallyの実行原理
finally文ブロックは、転送文を制御する前に実行されるべきであり、制御転送文にはreturnのほかにbreakとcontinueがある.またthrow文も制御遷移文に属する.return、throw、break、continueはいずれも制御遷移文であるが、それらの間には違いがある.ここでreturnとthrowはプログラム制御権を呼び出し者(invoker)に渡し、breakとcontinueの制御権は現在の方法内で移行する.
実際、JVMはfinally文ブロックをサブルーチンとしてtry文ブロックまたはcatch文ブロックの制御遷移文の前に直接挿入します.しかし、subroutine(すなわちfinally文ブロック)を実行する前にtryまたはcatch文ブロックがローカル変数テーブル(Local Variable Table)に戻り値を保持するという無視できない要素もあります.subroutineの実行が完了したら、保持された戻り値をオペランドスタックに復元し、returnまたはthrow文を使用してメソッドの呼び出し元(invoker)に返します.
前にreturn、throw、break、continueの違いについて述べたように、このルール(戻り値を保持)ではreturn文とthrow文にのみ適用され、break文とcontinue文には適用されません.戻り値がないからです. 
 
以上の説明では、finally文にreturn文が戻り値を上書きしていない場合、元の戻り値はfinallyの変更によって変更される可能性があり、変更されない可能性があります.
finallyが戻り値を変更していない場合
    public static int test55() {
        int b = 20;
        try {
            System.out.println("try block");
            return b += 80;
        } catch (Exception e) {
            System.out.println("catch block");
        } finally {
            System.out.println("finally block");

            if (b > 25) {
                System.out.println("b>25, b = " + b);
            }
            b = 150;
            System.out.println("finally, b =" + b);
        }
        return 2000;
    }
ここの結果は100でfinallyは受けませんでした コードブロックの影響.finallyを実行していないコードではなく、ポイントを中断して一歩一歩実行することができます.return b+=80の行コードの実行が開始されたことが分かった.このときb=100、finallyでb 確かに150です.しかし、System.out.println(「finally,b=」+b)を実行した後、return b+=80の行コードにジャンプしたため、結果は100になった.return b+=80に相当して2回実行した.
ここからも説明しますが、finallyでは コードブロックの前にreturnがあります そしてfinally 実行する場合
ステップ1:returnは1回実行し、ローカル変数テーブル(Local Variable Table)に戻り値を保持します.
ステップ2:finallyコードブロックの内容を実行します.
ステップ3:finallyコードブロックを実行した後、再び最終的なreturnに来て、そのメソッドの呼び出し者(invoker)に返します.
finally 戻り値を変更
    public static Map getMap() {
        Map map = new HashMap();
        map.put("KEY", "INIT");

        try {
            map.put("KEY", "TRY");
            return map;
        }
        catch (Exception e) {
            map.put("KEY", "CATCH");
        }
        finally {
            map.put("KEY", "FINALLY");
            map = null;
        }
        return map;
    }
ここでの戻り結果はFINALLYです.finallyが実行されました コードブロック、mapも確かにnullです.どうしてですか.やはり上記の3つのステップに従って分析します.
最初のステップでreturn mapを初めて実行した場合、map タブで行います. valueはTRYです.JVMでは、値mapが返されます ローカル変数テーブルに保持されます.
ステップ2ではfinallyコードブロックを実行し、まず変更します. map キーをKEYにする 値はTRY キーをKEYに変更 値はFINALLY、map の参照をnullに設定します.説明するのは、実際に作成されたMapオブジェクトは、finallyではなくメモリに存在します(スタック内にあります). 最後にmapを の参照はnullに設定されていますが、スタック内のMapオブジェクトの内容には影響しません.このときスタック内のMapオブジェクトのvalue やはりFINALLYです.
ステップ3 finally コードブロックの実行が終了してからreturn mapを実行すると、以前に保存していたmapが取り出されるので、ここのmapはnullではなく、スタック内のMapオブジェクトの内容を指しています.やはり注意しなければならないのは、ここではMapオブジェクトを返すことです.Stringタイプの変数を返すのではなく、少し特殊です.Stringタイプの変数を返すのではなく、StringだけならTRYを返すべきです.ここではKEYに基づいています. をクリックして対応する値を取得し、スタック内のMapオブジェクトでキーに基づいてKEY 得られた値はFINALLYです.
異常がある場合
    public static int test44() {
        int b = 20;
        try {
            System.out.println("try block");
            b = b / 0;
            return b += 80;
        } catch (Exception e) {
            b += 15;
            System.out.println("catch block");
        } finally {
            System.out.println("finally block");
            if (b > 25) {
                System.out.println("b>25, b = " + b);
            }
            b += 50;
        }
        return 204;
    }
ここで戻りは201ですが、これはよく理解できます. return b+=80の前の行b=b/0が間違っていると、return b+=80の行は実行されず、catchコードブロックに直接入って実行され、finallyに入ります. ブロック実行、finally のb=85、finally 実行が完了すると、最後の行まで実行されて204に戻る.最後の行が戻るbであれば、このとき85である.ここでよく分析してよく理解するのは、returnが最後に、順序的に自然であるためで、上の2つの例のreturn 前にいて、結局飛び上がった.
 
まとめ
もしtry 文は実行されませんでした(tryなど). 前に異常を投げ出したり戻したり)、tryで システム.out(0)が呼び出された場合、JVMが終了しました. は実行されません.
finally文ブロックは、tryまたはcatchまたはfinallyまたは他の場所(最後の行にreturnがあるなど)のreturn文の前に実行されます.次に、catchがある場合とない場合について説明します.    1,catchがある場合、まず異常がある場合にcatchを実行する.          a、catchで異常を投げ出すとfinallyが先に実行されます.          b,catchにreturnがあると最終結果はcatchにおけるreturnとなり,          c,ただし同時にfinallyにreturnがある場合は,異常の有無にかかわらず最終結果はfinallyにおけるreturnである.異常がなければ実行されません                             catch.     2,catchがない場合,tryとfinallyのみである.異常があるかどうかにかかわらずcatchは自然に実行されません.catchコードがないからです.          a,異常がある場合、                tryコードの前に異常が発生した場合(キャプチャされていない)、異常箇所の後は実行されず、finallyも実行されません                      (すなわち、finallyに対応するtry文ブロックが実行された場合にのみ、finally文ブロックが実行される).                異常がtryに現れるとfinallyが実行されます.          b,異常がなければ、                 finallyにreturnがある場合、最終結果はfinallyのreturnである.                 finallyにreturnがない場合、他のreturnを最終結果とします.
 
参考内容
1,Javaにおけるfinally文ブロックの試行判別についてhttps://www.ibm.com/developerworks/cn/java/j-lo-finally/index.html
2 Java finally文はreturnの前に実行されるのか、それとも後に実行されるのか.https://zhuanlan.zhihu.com/p/85377954