197

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

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

4

21 に答える 21

138

パフォーマンス:

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

ここに参照があります: http://www.javaworld.com/javaworld/jw-01-1997/jw-01-hood.html

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

于 2008-09-26T20:09:17.670 に答える
78

パフォーマンス: 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;
    }
}
于 2008-09-26T19:57:12.067 に答える
48

よし、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 の方法のいずれかに固執します。

于 2008-09-29T16:55:35.973 に答える
15

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

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

そして、他の出力 (try/catch in while):

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

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

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

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

于 2015-10-30T12:27:33.797 に答える
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;
}
于 2008-09-30T20:42:21.350 に答える
5

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

于 2009-02-13T01:22:16.777 に答える
4

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

于 2008-09-26T19:59:38.333 に答える
4

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

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

于 2012-08-31T09:08:33.973 に答える
2

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

于 2008-09-26T19:55:34.397 に答える
2

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

別の注意として、おそらく float.TryParse または Convert.ToFloat を確認する必要があります。

于 2008-09-26T19:57:18.073 に答える
2

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

于 2008-09-26T19:58:23.937 に答える
2

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

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

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

于 2008-10-05T15:41:53.697 に答える
2

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

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

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

于 2018-12-23T14:59:58.193 に答える
1

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

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

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

于 2010-03-11T19:06:16.940 に答える
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回だけです。

于 2008-10-09T16:30:41.803 に答える
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をスローする方がよい場合があります。

于 2008-10-07T08:13:14.897 に答える
1

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


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

于 2008-09-26T19:57:38.417 に答える
1

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

検討:

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

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

于 2008-10-01T05:23:00.147 に答える
1

例外の全体的なポイントは、最初のスタイルを奨励することです。つまり、エラー処理を統合して一度に処理できるようにすることです。エラーの可能性のあるすべてのサイトですぐに処理するのではありません。

于 2008-09-26T23:11:28.013 に答える
1

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

反復回数に応じて、パフォーマンスの違いはほとんどありません。

ただし、ループの外に置くとループ本体がきれいに見えるという他の人に同意します。

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

于 2008-09-26T20:01:47.097 に答える