配列 - java 文字 入力




試してみてください...ループの内側か外側かをキャッチしてください。 (13)

try / catch用の特別なスタックフレームを設定するとオーバーヘッドが増えますが、JVMは戻り値を検出してこれを最適化できる可能性があります。

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

しかし、私は他のものには、ループの外にそれを持っていることがループのボディをよりきれいに見せることに同意します。

無効な数値がある場合に終了するのではなく、処理を続行したい場合は、コードをループ内に置くことをお勧めします。

私はこのようなループを持っています:

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


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


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

そのループが人生でキューを空にする役割を持っていた場合、そのキューは本当に空になる前に終了する可能性が非常に高いです。 非常に一般的な欠陥。


あなたの例では、機能上の違いはありません。 あなたの最初の例をもっと読みやすくしています。


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


すべてのパフォーマンスと可読性の高い投稿に同意します。 しかし、本当に重要な場合があります。 いくつかの人がこのことについて言及しましたが、例を見れば分かりやすいかもしれません。

この少し修正された例を考えてみましょう:

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番目のフォームを使用する必要があります。 それらの方法の選択のための私の基本的な基準になります。 個人的には、それが全部でもなくても、私は2番目の形式を使用しません。


パフォーマンスは同じで、よりよく見えるものは非常に主観的ですが、機能にはかなり大きな違いがあります。 次の例を考えてみましょう。

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

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

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

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


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


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

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

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


例外の全ポイントは、最初のスタイルを奨励することです。エラー処理をすべてのエラーサイトで直ちに行うのではなく、一度統合して処理することです。


内部にあれば、try / catch構造のオーバーヘッドをN回取得します。

Try / Catch構造体が呼び出されるたびに、メソッドの実行にオーバーヘッドが追加されます。 構造に対処するために必要なちょっとしたメモリとプロセッサティック。 ループを100回実行していて仮説のためにコストが試行/キャッチコールごとに1ティックであると仮定した場合、ループ内の試行/キャッチには100ティックがかかりますループの外側にあります。


私は、例外処理を配置する場所の一般的な問題を見るときに、2つの競合する考慮0.02cについて自分自身の0.02cを追加するのが好きです。

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

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





try-catch