webdevqa.jp.net

しよう...キャッチループの内側または外側に行く?

次のようなループがあります。

for (int i = 0; i < max; i++) {
    String myString = ...;
    float myNum = Float.parseFloat(myString);
    myFloats[i] = myNum;
}

これは、floatの配列を返すことを唯一の目的とするメソッドのメインコンテンツです。エラーがある場合、このメソッドがnullを返すようにするため、次のようにtry...catchブロック内にループを配置します。

try {
    for (int i = 0; i < max; i++) {
        String myString = ...;
        float myNum = Float.parseFloat(myString);
        myFloats[i] = myNum;
    }
} catch (NumberFormatException ex) {
    return null;
}

しかし、次のように、ループ内にtry...catchブロックを配置することも考えました。

for (int i = 0; i < max; i++) {
    String myString = ...;
    try {
        float myNum = Float.parseFloat(myString);
    } catch (NumberFormatException ex) {
        return null;
    }
    myFloats[i] = myNum;
}

どちらか一方を好む理由、パフォーマンスまたはその他の理由はありますか?


編集:コンセンサスは、ループをtry/catch内、おそらく独自のメソッド内に配置する方がクリーンであるようです。しかし、より速い議論がまだあります。誰かがこれをテストして、統一された答えを返すことができますか?

171
Michael Myers

Jeffrey L Whitledgeが言った パフォーマンスの違いはなかった(1997年現在)後、私は行ってテストしました。私はこの小さなベンチマークを実行しました:

public class Main {

    private static final int NUM_TESTS = 100;
    private static int ITERATIONS = 1000000;
    // time counters
    private static long inTime = 0L;
    private static long aroundTime = 0L;

    public static void main(String[] args) {
        for (int i = 0; i < NUM_TESTS; i++) {
            test();
            ITERATIONS += 1; // so the tests don't always return the same number
        }
        System.out.println("Inside loop: " + (inTime/1000000.0) + " ms.");
        System.out.println("Around loop: " + (aroundTime/1000000.0) + " ms.");
    }
    public static void test() {
        aroundTime += testAround();
        inTime += testIn();
    }
    public static long testIn() {
        long start = System.nanoTime();
        Integer i = tryInLoop();
        long ret = System.nanoTime() - start;
        System.out.println(i); // don't optimize it away
        return ret;
    }
    public static long testAround() {
        long start = System.nanoTime();
        Integer i = tryAroundLoop();
        long ret = System.nanoTime() - start;
        System.out.println(i); // don't optimize it away
        return ret;
    }
    public static Integer tryInLoop() {
        int count = 0;
        for (int i = 0; i < ITERATIONS; i++) {
            try {
                count = Integer.parseInt(Integer.toString(count)) + 1;
            } catch (NumberFormatException ex) {
                return null;
            }
        }
        return count;
    }
    public static Integer tryAroundLoop() {
        int count = 0;
        try {
            for (int i = 0; i < ITERATIONS; i++) {
                count = Integer.parseInt(Integer.toString(count)) + 1;
            }
            return count;
        } catch (NumberFormatException ex) {
            return null;
        }
    }
}

Javapを使用して結果のバイトコードをチェックし、インライン化されていないことを確認しました。

結果は、重要でないJIT最適化を想定して、Jeffreyが正しいことを示しました;絶対にJava 6、SunクライアントVMにパフォーマンスの違いはありません(他のバージョンにアクセスできませんでした)。合計時間差は、テスト全体で数ミリ秒のオーダーです。

したがって、唯一の考慮事項は、最もきれいに見えるものです。 2番目の方法は見苦しいため、最初の方法または Ray Hayesの方法 に固執します。

46
Michael Myers

性能:

Try/catch構造が配置される場所にパフォーマンスの違いはまったくありません。内部的には、メソッドが呼び出されたときに作成される構造内のコード範囲テーブルとして実装されます。メソッドの実行中、try/catch構造は、スローが発生しない限り完全に見えなくなり、エラーの場所がテーブルと比較されます。

リファレンスは次のとおりです。 http://www.javaworld.com/javaworld/jw-01-1997/jw-01-hood.html

この表は、中途半端に記述されています。

122

パフォーマンスJeffrey が彼の返事で言ったように、Javaであまり違いはありません。

一般的に、コードを読みやすくするために、例外をキャッチする場所の選択は、ループで処理を継続するかどうかによって異なります。

あなたの例では、例外をキャッチすると戻りました。その場合、ループの周りにtry/catchを配置します。単に悪い値をキャッチして処理を続行したい場合は、その値を内部に配置します。

3番目の方法:常に独自の静的ParseFloatメソッドを記述し、ループではなくそのメソッドで例外処理を処理することができます。例外処理をループ自体に分離します!

class Parsing
{
    public static Float MyParseFloat(string inputValue)
    {
        try
        {
            return Float.parseFloat(inputValue);
        }
        catch ( NumberFormatException e )
        {
            return null;
        }
    }

    // ....  your code
    for(int i = 0; i < max; i++) 
    {
        String myString = ...;
        Float myNum = Parsing.MyParseFloat(myString);
        if ( myNum == null ) return;
        myFloats[i] = (float) myNum;
    }
}
69
Ray Hayes

パフォーマンスは同じである可能性があり、「見た目」が良くなるのは非常に主観的ですが、機能にはかなり大きな違いがあります。次の例をご覧ください。

Integer j = 0;
    try {
        while (true) {
            ++j;

            if (j == 20) { throw new Exception(); }
            if (j%4 == 0) { System.out.println(j); }
            if (j == 40) { break; }
        }
    } catch (Exception e) {
        System.out.println("in catch block");
    }

Whileループはtry catchブロック内にあり、変数 'j'は40に達するまでインクリメントされ、j mod 4がゼロのときに出力され、jが20に達すると例外がスローされます。

詳細の前に、他の例を示します。

Integer i = 0;
    while (true) {
        try {
            ++i;

            if (i == 20) { throw new Exception(); }
            if (i%4 == 0) { System.out.println(i); }
            if (i == 40) { break; }

        } catch (Exception e) { System.out.println("in catch block"); }
    }

上記と同じロジックですが、唯一の違いは、try/catchブロックがwhileループ内にあることです。

出力は次のとおりです(try/catch中)。

4
8
12 
16
in catch block

そして、他の出力(whileで試行/キャッチ):

4
8
12
16
in catch block
24
28
32
36
40

そこにはかなり大きな違いがあります:

try/catchではループから抜け出します

ループをアクティブに保ちながら、try/catch in

14
seBaka28

パフォーマンスと読みやすさに関するすべての投稿に同意します。ただし、実際に問題になる場合があります。他にも数人の人がこれについて言及しましたが、例を見るとわかりやすいかもしれません。

このわずかに変更された例を考えてみましょう:

public static void main(String[] args) {
    String[] myNumberStrings = new String[] {"1.2345", "asdf", "2.3456"};
    ArrayList asNumbers = parseAll(myNumberStrings);
}

public static ArrayList parseAll(String[] numberStrings){
    ArrayList myFloats = new ArrayList();

    for(int i = 0; i < numberStrings.length; i++){
        myFloats.add(new Float(numberStrings[i]));
    }
    return myFloats;
}

(元の例のように)エラーがある場合にparseAll()メソッドがnullを返すようにするには、次のように外側にtry/catchを配置します。

public static ArrayList parseAll1(String[] numberStrings){
    ArrayList myFloats = new ArrayList();
    try{
        for(int i = 0; i < numberStrings.length; i++){
            myFloats.add(new Float(numberStrings[i]));
        }
    } catch (NumberFormatException nfe){
        //fail on any error
        return null;
    }
    return myFloats;
}

実際には、おそらくnullの代わりにここでエラーを返す必要があります。一般的に、複数の戻り値を持つことは好きではありませんが、アイデアは得られます。

一方、問題を無視して、可能な文字列を解析する場合は、次のようにループの内側にtry/catchを配置します。

public static ArrayList parseAll2(String[] numberStrings){
    ArrayList myFloats = new ArrayList();

    for(int i = 0; i < numberStrings.length; i++){
        try{
            myFloats.add(new Float(numberStrings[i]));
        } catch (NumberFormatException nfe){
            //don't add just this one
        }
    }

    return myFloats;
}
13
Matt N

すでに述べたように、パフォーマンスは同じです。ただし、ユーザーエクスペリエンスは必ずしも同一ではありません。最初のケースでは、高速に(つまり、最初のエラーの後)失敗しますが、try/catchブロックをループ内に配置すると、メソッドの特定の呼び出しに対して作成されるすべてのエラーをキャプチャできます。書式設定エラーが予想される文字列から値の配列を解析するとき、ユーザーがエラーを1つずつ修正しようとする必要がないように、すべてのエラーをユーザーに提示できるようにしたい場合があります。 。

5
user19810

その全か無かが失敗する場合、最初の形式は理にかなっています。すべての非障害要素を処理/返却できるようにする場合は、2番目の形式を使用する必要があります。これらは、メソッドを選択するための基本的な基準になります。個人的に、それがオールオアナッシングである場合、2番目のフォームは使用しません。

4
Joe Skora

ループで何を達成する必要があるかを認識している限り、try catchをループの外側に置くことができます。しかし、例外が発生するとすぐにループが終了し、それが常に望んでいるとは限らないことを理解することが重要です。これは実際、Javaベースのソフトウェアで非常に一般的なエラーです。キューを空にするなど、多くのアイテムを処理する必要があり、考えられるすべての例外を処理する外側のtry/catchステートメントに誤って依存します。また、ループ内で特定の例外のみを処理し、他の例外が発生することを期待していません。次に、ループ内で処理されない例外が発生した場合、ループは「プリエンプト」され、場合によっては終了し、外側のcatchステートメントが例外を処理します。

キューを空にするという役割がループにある場合、そのループは、キューが実際に空になる前に終了する可能性が非常に高くなります。非常に一般的な障害。

私の見方では、適切な例外処理を保証するにはtry/catchブロックが必要ですが、そのようなブロックを作成するとパフォーマンスに影響します。ループには集中的な反復計算が含まれるため、ループ内にtry/catchブロックを配置することは推奨されません。さらに、この状態が発生する場所は、多くの場合「Exception」または「RuntimeException」であると思われます。 RuntimeExceptionがコードでキャッチされるのを避ける必要があります。繰り返しますが、大企業で働いている場合は、その例外を適切にログに記録するか、実行時例外を停止することが不可欠です。この説明の要点はPLEASE AVOID USING TRY-CATCH BLOCKS IN LOOPSです

2
surendrapanday

あなたの例では、機能的な違いはありません。最初の例の方が読みやすいと思います。

2
Jamie

内部バージョンよりも外部バージョンを優先する必要があります。これはルールの特定のバージョンであり、ループの外側に移動できるものはすべてループの外側に移動します。 ILコンパイラとJITコンパイラに応じて、2つのバージョンは異なるパフォーマンス特性になる場合とそうでない場合があります。

別のメモでは、おそらくfloat.TryParseまたはConvert.ToFloatを参照する必要があります。

2
Orion Adrian

Try/catchをループ内に配置すると、例外の後もループを続けます。ループの外側に配置すると、例外がスローされるとすぐに停止します。

2
Joel Coehoorn

内側にある場合、外側に1回だけではなく、try/catch構造のオーバーヘッドをN回取得します。


Try/Catch構造体が呼び出されるたびに、メソッドの実行にオーバーヘッドが追加されます。構造を処理するために必要なメモリとプロセッサティックのほんの少し。ループを100回実行している場合、仮にコストがtry/catch呼び出しごとに1ティックであり、ループ内でTry/Catchを使用すると、100ティックのコストがかかります。ループの外側。

1

try/catchに特別なスタックフレームを設定すると、オーバーヘッドが追加されますが、JVMは、返されているという事実を検出し、これを最適化することができます。

反復の回数によっては、パフォーマンスの違いは無視できる程度です。

しかし、ループの外側にあるとループ本体がきれいに見えるという他の人に同意します。

無効な番号がある場合に終了するのではなく、処理を続行する可能性がある場合は、コードをループ内に配置する必要があります。

1
Matt

例外の全体的なポイントは、最初のスタイルを奨励することです。エラー処理をすべての可能なエラーサイトですぐにではなく、統合して1回処理することです。

1
wnoise

例外処理を配置する場所の一般的な問題を検討する際に、競合する2つの考慮事項について、独自の0.02cを追加したいと思います。

  1. try-catchブロックの「より広い」責任(つまり、あなたの場合のループの外側)は、後でコードを変更するときに、既存のcatchブロックで処理される行を誤って追加する可能性があることを意味します。おそらく意図せず。あなたの場合、NumberFormatExceptionを明示的にキャッチしているため、これはあまり起こりません

  2. try-catchブロックの責任が「狭い」ほど、リファクタリングは難しくなります。特に(あなたの場合のように)catchブロック(return nullステートメント)内から「非ローカル」命令を実行している場合。

1
oxbow_lakes

それは障害処理に依存します。エラー要素をスキップしたい場合は、内部で試してください:

for(int i = 0; i < max; i++) {
    String myString = ...;
    try {
        float myNum = Float.parseFloat(myString);
        myFloats[i] = myNum;
    } catch (NumberFormatException ex) {
        --i;
    }
}

それ以外の場合は、屋外で試してみることをお勧めします。コードはより読みやすく、よりクリーンです。 nullを返す場合は、代わりにエラーの場合にIllegalArgumentExceptionをスローする方が良いでしょう。

1
Arne Burmeister

中に置く。必要に応じて処理を続行することも、クライアントにmyStringの値と不正な値を含む配列のインデックスを通知する有用な例外をスローすることもできます。 NumberFormatExceptionはすでに悪い値を教えてくれると思いますが、原則はスローする例外にすべての有用なデータを配置することです。プログラムのこの時点でデバッガで何が面白いかを考えてください。

考慮してください:

try {
   // parse
} catch (NumberFormatException nfe){
   throw new RuntimeException("Could not parse as a Float: [" + myString + 
                              "] found at index: " + i, nfe);
} 

必要なときに、可能な限り多くの情報を含むこのような例外に本当に感謝します。

1
Kyle Dyer

$ 0.02を投入します。コードに「最後に」追加する必要が生じる場合があります(コードを最初に完全に作成した人は誰ですか?)。これらの場合、突然ループの外側でtry/catchを行う方が理にかなっています。例えば:

try {
    for(int i = 0; i < max; i++) {
        String myString = ...;
        float myNum = Float.parseFloat(myString);
        dbConnection.update("MY_FLOATS","INDEX",i,"VALUE",myNum);
    }
} catch (NumberFormatException ex) {
    return null;
} finally {
    dbConnection.release();  // Always release DB connection, even if transaction fails.
}

エラーが発生した場合、または発生しなかった場合、データベース接続を解放する(または、お気に入りのタイプのその他のリソースを選択する)必要があるのは一度だけです。

1
Ogre Psalm33

上記で言及されていない別の側面は、すべてのtry-catchがスタックにsomeの影響を与えるという事実です。これは、再帰メソッドに影響を与える可能性があります。

メソッド "outer()"がメソッド "inner()"(それ自体を再帰的に呼び出す可能性がある)を呼び出す場合、可能であればメソッド "outer()"でtry-catchを見つけてください。パフォーマンスクラスで使用する単純な「スタッククラッシュ」の例は、try-catchがinnerメソッドにある場合は約6,400フレームで失敗し、outerメソッドにある場合は約11,600フレームで失敗します。

現実の世界では、Compositeパターンを使用していて、大きく複雑なネスト構造がある場合、これが問題になる可能性があります。

1
Jeff Hill

反復ごとに例外をキャッチしたい場合、またはどの反復例外がスローされたかを確認して、反復内のすべての例外をキャッチしたい場合は、ループ内にtry ... catchを配置します。例外が発生してもループは中断されず、ループ全体の各反復ですべての例外をキャッチできます。

ループを中断し、スローされるたびに例外を調べたい場合は、ループからtry ... catchを使用します。これにより、ループが中断され、catch(存在する場合)の後にステートメントが実行されます。

それはすべてあなたのニーズ次第です。展開中にループ内でtry ... catchを使用することをお勧めします。例外が発生した場合、結果はあいまいではなく、ループは完全に壊れて実行されません。

0