5

finallyブロックの動作を理解するために、7 つのテスト ケースを作成しました。どのようにfinally機能するかの背後にあるロジックは何ですか?

package core;

public class Test {
    public static void main(String[] args) {
        new Test().testFinally();
    }

    public void testFinally() {
        System.out.println("One = " + tryOne());
        System.out.println("Two = " + tryTwo());
        System.out.println("Three = " + tryThree());
        System.out.println("Four = " + tryFour());
        System.out.println("Five = " + tryFive());
        System.out.println("Six = " + trySix());
        System.out.println("Seven = " + trySeven());
    }

    protected StringBuilder tryOne() {
        StringBuilder builder = new StringBuilder();
        try {
            builder.append("Cool");
            return builder.append("Return");
        }
        finally {
            builder = null;
        }
    }

    protected String tryTwo() {
        String builder = "Cool";
        try {
            return builder += "Return";
        }
        finally {
            builder = null;
        }
    }

    protected int tryThree() {
        int builder = 99;
        try {
            return builder += 1;
        }
        finally {
            builder = 0;
        }
    }

    protected StringBuilder tryFour() {
        StringBuilder builder = new StringBuilder();
        try {
            builder.append("Cool");
            return builder.append("Return");
        }
        finally {
            builder.append("+1");
        }
    }

    protected int tryFive() {
        int count = 0;
        try {
            count = 99;
        }
        finally {
            count++;
        }
        return count;
    }

    protected int trySix() {
        int count = 0;
        try {
            count = 99;
        }
        finally {
            count = 1;
        }
        return count;
    }

    protected int trySeven() {
        int count = 0;
        try {
            count = 99;
            return count;
        }
        finally {
            count++;
        }
    }
}

なぜbuilder = null機能しないのですか?

( in builder.append("+1")trySeven ()) が機能しないのになぜ機能するのですか?count++

4

6 に答える 6

10

return を行ったら、それをオーバーライドする唯一の方法は、別の return を行うことです ( Returning from a finally block in Javaで説明されているように、これはほとんど常に悪い考えです)、そうでなければ突然完了することです。あなたのテストは、最終的に返されることはありません。

JLS § 14.1は、突然の完了を定義しています。突然の完了タイプの 1 つに return があります。リターンにより、1、2、3、4、および 7 の try ブロックが突然完了します。§ 14.20.2で説明されているように、try ブロックがスロー以外の理由 R で突然完了すると、finally ブロックがすぐに実行されます。

finally ブロックが正常に完了した場合 (特に、何も返されないことを意味します)、「try ステートメントは理由 R のために突然完了します」。つまり、try によって開始されたリターンはそのまま残ります。これはすべてのテストに適用されます。finally から戻ると、「try ステートメントは理由 S で突然完了します (理由 R は破棄されます)」。(S ここでは、新しいオーバーライド リターンです)。

したがって、tryOne では、次のようにします。

finally {
            builder = null;
            return builder;
        }

この新しい戻り値 S は、元の戻り値 R をオーバーライドします。

builder.append("+1")inの場合tryFour、StringBuilder は可変であるため、try で指定された同じオブジェクトへの参照を引き続き返すことに注意してください。あなたは土壇場で突然変異をしているだけです。

tryFiveそしてtrySix率直です。try は return がないので、try と finally はどちらも正常に完了し、try-finally がなかったかのように実行されます。

于 2010-07-12T05:45:23.780 に答える
2

より頻繁に目にするユースケースから始めましょう -リークを避けるために閉じなければならないリソースがあります。

public void deleteRows(Connection conn) throws SQLException {
    Statement statement = conn.createStatement();
    try {
        statement.execute("DELETE * FROM foo");
    } finally {
        statement.close();
    }
}

この場合、完了したらステートメントを閉じる必要があるため、データベース リソースがリークすることはありません。これにより、例外がスローされた場合に、関数が終了する前にステートメントを常に閉じることが保証されます。

try { ... } finally { ... } ブロックは、メソッドが終了したときに常に何かが実行されるようにするためのものです。例外の場合に最も役立ちます。次のようなことをしている場合:

public String thisShouldBeRefactored(List<String> foo) {
    try { 
        if(foo == null) {
            return null;
        } else if(foo.length == 1) {
            return foo.get(0);
        } else {
            return foo.get(1);
        }
    } finally {
        System.out.println("Exiting function!");
    }
}

あなたは本当に最終的に適切に使用していません。これには、パフォーマンス上のペナルティがあります。クリーンアップする必要がある例外ケースがある場合は、これを使用してください。上記をこれにリファクタリングしてみてください:

public String thisShouldBeRefactored(List<String> foo) {
    final String result;

    if(foo == null) {
        result = null;
    } else if(foo.length == 1) {
        result = foo.get(0);
    } else {
        result = foo.get(1);
    }

    System.out.println("Exiting function!");

    return result;
}
于 2010-07-12T05:52:26.087 に答える
2

try ブロックを終了すると、finally ブロックが実行されます。「return」ステートメントは 2 つのことを行います。1 つは関数の戻り値を設定すること、もう 1 つは関数を終了することです。通常、これはアトミック操作のように見えますが、try ブロック内では、戻り値が設定された後、関数が終了する前に、finally ブロックが実行されます。

実行を返す:

  1. 戻り値を割り当てる
  2. 最終ブロックを実行
  3. 出口機能

例 1 (プリミティブ):

int count = 1;//Assign local primitive count to 1
try{
  return count; //Assign primitive return value to count (1)
}finally{
  count++ //Updates count but not return value
}

例 2 (参照):

StringBuilder sb = new StringBuilder();//Assign sb a new StringBuilder
try{
    return sb;//return a reference to StringBuilder
}finally{
    sb.append("hello");//modifies the returned StringBuilder
}

例 3 (参照):

   StringBuilder sb = new StringBuilder();//Assign sb a new StringBuilder
   try{
      return sb;//return a reference to StringBuilder
   }finally{
      sb = null;//Update local reference sb not return value
   }

例 4 (リターン):

   int count = 1;   //assign count
   try{
      return count; //return current value of count (1)
   }finally{
      count++;      //update count to two but not return value
      return count; //return current value of count (2) 
                    //replaces old return value and exits the finally block
   }
于 2010-07-12T06:22:52.637 に答える
0

なぜbuilder = null機能しないのですか?
ローカル参照を null に設定しているため、メモリの内容は変更されません。最終的にブロックした後にビルダーにアクセスしようとすると、null になります。
理由builder.append("+1") work?
参照を使用してメモリの内容を変更しているため、それが機能するはずです。testFive() で機能しないのは
なぜですか? それは私とうまくいっています。期待どおり 100 が出力されます。count++

于 2010-07-12T05:57:21.067 に答える
0

builder = nullと働いていますbuilder.append("+1") 返されるものに影響を与えていないだけです。この関数はreturn、後で何が起こるかに関係なく、ステートメントが持っているものを返します。

違いがある理由builderは、 が参照によって渡されるためです。 のローカルコピーをbuilder=null変更します。 親が保持するコピーに影響します。builderbuilder.append("+1")

于 2010-07-12T05:45:05.040 に答える
0

たとえば、tryOne() で、コンパイラが return ステートメントに対して実際に行っていることを考えてみましょう。これは、参照をbuilder呼び出し元の関数の環境にコピーします。これが完了した後、制御が呼び出し元の関数に戻る前に、finally ブロックが実行されます。したがって、実際には次のようなものがあります。

protected StringBuilder tryOne() {
    StringBuilder builder = new StringBuilder();
    try {
        builder.append("Cool");
        builder.append("Return");
        StringBuilder temp = builder;
        return temp;
    } finally {
        builder = null;
    }
}

または、ステートメントが実際に実行される順序に関しては (もちろん、考えられる例外は無視します)、次のようになります。

protected StringBuilder tryOne() {
    StringBuilder builder = new StringBuilder();
    builder.append("Cool");
    builder.append("Return");
    StringBuilder temp = builder;
    builder = null;
    return temp;
}

したがって、設定builder = nullは実行されますが、何も役に立ちません。ただし、temp と builder の両方が同じ (変更可能な) オブジェクトを参照するため、実行builder.append("something") すると目に見える効果があります。

同様に、trySeven() で実際に起こっていることは、次のようなものです。

protected int trySeven() {
    int count = 0;
    count = 99;
    int temp = count;
    count++;
    return temp;
}

この場合、int を扱っているため、コピーは独立しているため、一方をインクリメントしても他方には影響しません。

そうは言っても、return ステートメントを try-finally ブロックに入れるのは明らかに混乱を招くという事実は残っています。 try-finally ブロックの外にあります。

于 2010-07-12T06:13:39.577 に答える