キャッチする必要があります...キャッチはループの内側または外側に行きますか?


185

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

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

これは、フロートの配列を返すことを唯一の目的とするメソッドの主要なコンテンツです。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内、おそらく独自のメソッド内にループを置く方がクリーンであるようです。ただし、どちらがより速いかについてはまだ議論があります。誰かがこれをテストして統一された答えを返すことができますか?


2
パフォーマンスについてはわかりませんが、forループの外側でtry catchを使用すると、コードがきれいに見えます。
ジャックBニンブル

回答:


132

パフォーマンス:

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

これがリファレンスです:http : //www.javaworld.com/javaworld/jw-01-1997/jw-01-hood.html

表は半分ほど下に記述されています。


さて、あなたは美学以外に違いはないと言っています。ソースにリンクできますか?
マイケルマイヤーズ

2
ありがとう。1997年以降は状況が変わったかもしれませんが、効率が悪くなることはほとんどありません
マイケルマイヤーズ

1
絶対に難しい言葉です。場合によっては、コンパイラの最適化を妨げることがあります。直接的なコストではありませんが、間接的にパフォーマンスが低下する可能性があります。
トムホーティン-タックライン'09 / 09/27

2
確かに、私は少し熱意に君臨していたはずだと思いますが、誤った情報が私の神経に伝わってきました!最適化の場合、パフォーマンスにどのような影響が及ぶかわからないので、(いつものように)試してみることに戻ったと思います。
Jeffrey L Whitledge、2008

1
コードが例外なく実行される場合のみ、パフォーマンスに違いはありません。
OnurBıyık10年

72

パフォーマンスジェフリーが彼の返事で言ったように、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;
    }
}

1
よく見てください。彼は両方の最初の例外で終了します。私も最初は同じことを考えていました。
kervin 2008

2
彼が問題にぶつかったときに彼が戻ってきているので、私は外部キャプチャを使用することになるので、私は彼のケースで私が言った理由を知っていました。明確にするために、それは私が使用したいルールだ...
レイ・ヘイズ

素晴らしいコードですが、残念ながらJavaには出力パラメーターの贅沢がありません。
マイケルマイヤーズ

ByRef?私がJavaに触れてからずっと経ちました!
レイ・ヘイズ

2
他の誰かがC#/。netで例外を処理せずに安全な解析を実行できるTryParseを提案しました。これは複製され、メソッドは再利用可能です。元の質問については、私はキャッチの内側ループを好むだろう、それだけで何のパフォーマンス上の問題がなくてもきれいに見える...
レイ・ヘイズ

46

(1997年の時点で)パフォーマンスの違いはないとジェフリーLホイットレッジが言った後、私は行ってテストしました。私はこの小さなベンチマークを実行しました:

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番目の方法は醜いので、最初の方法またはレイヘイズの方法のどちらかに固執します。


例外ハンドラーがループの外でフローを取得する場合は、ループのに配置するのが最も明確だと思います。このような「catch-all」メソッドの「caught」本体の周りに空白行を使用して、すべてを囲むtryブロックから本体をより明確に分離しています
トーマスW

16

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

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

そして、他の出力(しばらくしてみてください/キャッチ):

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

そこではかなりの違いがあります:

try / catch中にループから抜けます

ループをアクティブに保ちながら試行/キャッチ


したがってtry{} catch() {}、ループが単一の「ユニット」として扱われ、そのユニットに問題が見つかった場合は、ループ全体を「ユニット」(またはこの場合はループ)が南に移動するように配置します。置くtry{} catch() {}あなたは、ループの1回の反復、または全体の「ユニット」として、アレイ内の単一の要素を処理している場合はループ内。その「ユニット」で例外が発生した場合、その要素をスキップして、ループの次の反復に進みます。
ギャラクシー

14

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

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

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;
}

2
だから、「悪い値だけをキャッチして処理を続けたいのなら、中に入れてください」と言いました。
レイ・ヘイズ

5

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


これは、この特定のケースには当てはまりません(私はcatchブロックで戻ります)が、一般的に覚えておくことは良いことです。
マイケルマイヤーズ

4

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


4

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

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


2

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


2

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

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


2

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


ええと、例外でnullを返しているので、私の場合は処理を続けられません。
マイケルマイヤーズ

2

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


1

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

反復回数によっては、パフォーマンスの違いはごくわずかです。

ただし、ループの外側に配置すると、ループのボディがきれいに見えるという点で他の人と同じです。

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


1

それが内部にある場合は、try / catch構造のオーバーヘッドをN回取得します。


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


オーバーヘッドについて少し詳しく説明していただけますか?
マイケルマイヤーズ

1
承知しましたが、ジェフリーLホイットリッジは追加のオーバーヘッドはないと述べています。あると言う情報源を提供できますか?
マイケルマイヤーズ

コストは小さく、ほとんど無視できますが、それはあります。.NETに関するProgrammer's Heaven(tiny.cc/ZBX1b)のブログ記事と、JAVA(tiny.cc/BV2hS)に関するReshat Sabiqからのニュースグループの投稿
スティーブンライトン

わかりました...では、問題は、try / catchに入るときに発生するコスト(この場合はループの外にある必要があります)か、try / catchの期間中一定ですか(この場合はループの中にある必要があります)?あなたはそれが#1だと言います。そして、私はまだコストがある完全には確信していません。
マイケルマイヤーズ

1
このGoogleブック(tinyurl.com/3pp7qj)によると、「最新のVMにはペナルティはありません」。
マイケルマイヤーズ

1

例外の要点は、最初のスタイルを奨励することです。つまり、すべての可能なエラーサイトですぐにではなく、エラー処理を統合して一度だけ処理できるようにします。


違う!例外のポイントは、「エラー」が発生したときに採用する例外的な動作を決定することです。したがって、内側または外側のtry / catchブロックを選択するかどうかは、ループ処理をどのように処理するかによって異なります。
ギズモ

これは、「エラーが発生しました」フラグを渡すなど、例外前のエラー処理でも同様に行うことができます。
wnoise 2008

1

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

考慮してください:

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

必要なときに、できるだけ多くの情報を含むこのような例外を本当に感謝します。


はい、これも有効なポイントです。私の場合、ファイルから読み取っているので、本当に必要なのは解析に失敗した文字列だけです。そのため、別の例外をスローする代わりにSystem.out.println(ex)を実行しました(正当なだけファイルを解析したい)。
マイケル・マイヤーズ

1

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

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

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


1

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

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

私は$ 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回だけだからです。


1

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

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

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


0

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

ループを解除して例外がスローされるたびに調べる場合は、ループの外でtr​​y ... catchを使用します。これにより、ループが中断され、catch(ある場合)の後にステートメントが実行されます。

それはすべてあなたの必要性に依存します。デプロイ中にループ内でtry ... catchを使用することをお勧めします。例外が発生した場合、結果があいまいではなく、ループが壊れて完全に実行されないためです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.