try… catchはループの内側または外側に行くべきですか?
-
02-07-2019 - |
質問
次のようなループがあります:
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内、おそらく独自のメソッド内に配置する方がクリーンであると思われます。しかし、より速い議論がまだあります。誰かがこれをテストして、統一された答えを返すことができますか?
解決 3
大丈夫、ジェフリーLホイットレッジが言った(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 / catch構造が配置される場所にパフォーマンスの違いはまったくありません。内部的には、メソッドの呼び出し時に作成される構造内のコード範囲テーブルとして実装されます。メソッドの実行中、try / catch構造は、スローが発生しない限り完全に見えなくなり、エラーの場所がテーブルと比較されます。
参照先: http:// www。 javaworld.com/javaworld/jw-01-1997/jw-01-hood.html
表は中途半端に記述されています。
パフォーマンス:ジェフリーが返信で言ったように、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;
}
}
パフォーマンスは同じかもしれませんが、「見た目」は何ですか?より良いことは非常に主観的ですが、機能にはまだかなり大きな違いがあります。次の例をご覧ください。
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でループから抜ける
ループをアクティブに保ちながら、試行/キャッチイン
パフォーマンスと読みやすさに関するすべての投稿に同意します。ただし、実際に問題になる場合があります。他にも数人の人がこれについて言及しましたが、例を見るとわかりやすいかもしれません。
このわずかに変更された例を検討してください:
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;
}
すでに述べたように、パフォーマンスは同じです。ただし、ユーザーエクスペリエンスは必ずしも同じではありません。最初のケースでは、高速に(つまり、最初のエラーの後)失敗しますが、try / catchブロックをループ内に配置すると、メソッドの特定の呼び出しに対して作成されるすべてのエラーをキャプチャできます。書式設定エラーが予想される文字列から値の配列を解析する場合、ユーザーがエラーを1つずつ修正する必要がないように、すべてのエラーをユーザーに提示できるようにしたい場合があります。 。
全か無かが失敗する場合、最初の形式は理にかなっています。すべての非障害要素を処理/返却できるようにする場合は、2番目の形式を使用する必要があります。これらは、メソッドを選択するための基本的な基準です。個人的に、それがオールオアナッシングの場合、2番目のフォームは使用しません。
ループで何を達成する必要があるかを認識している限り、try catchをループの外側に置くことができます。しかし、例外が発生するとすぐにループが終了し、それが常に望んでいるとは限らないことを理解することが重要です。これは実際、Javaベースのソフトウェアでは非常に一般的なエラーです。キューを空にするなど、多くのアイテムを処理する必要があり、考えられるすべての例外を処理する外側のtry / catchステートメントに誤って依存します。また、ループ内で特定の例外のみを処理し、他の例外が発生することを期待していません。 その後、ループ内で処理されない例外が発生すると、ループは「プリエムテッド」になり、おそらく早まって終了し、外側のcatchステートメントが例外を処理します。
キューを空にするという役割がループにある場合、そのループは、キューが実際に空になる前に終了する可能性が高いです。非常に一般的な障害。
例では、機能的な違いはありません。最初の例の方が読みやすいと思います。
内部バージョンよりも外部バージョンを優先する必要があります。これはルールの特定のバージョンであり、ループの外側に移動できるものはすべてループの外側に移動します。 ILコンパイラとJITコンパイラに応じて、2つのバージョンのパフォーマンス特性が異なる場合とそうでない場合があります。
別のメモでは、おそらくfloat.TryParseまたはConvert.ToFloatを参照する必要があります。
try / catchをループ内に配置すると、例外の後もループを続けます。ループの外側に配置すると、例外がスローされるとすぐに停止します。
適切な例外処理を保証するにはtry / catchブロックが必要ですが、そのようなブロックを作成するとパフォーマンスに影響します。ループには集中的な反復計算が含まれるため、ループ内にtry / catchブロックを配置することは推奨されません。また、この状態が発生する場所は、多くの場合「例外」です。または&quot; RuntimeException&quot;キャッチされます。 RuntimeExceptionがコードでキャッチされるのを避ける必要があります。繰り返しますが、大企業で働いている場合は、その例外を適切に記録するか、実行時例外を停止することが不可欠です。この説明の全体のポイントは、ループでトライキャッチブロックを使用しないでください
try / catchに特別なスタックフレームを設定するとオーバーヘッドが追加されますが、JVMは返された事実を検出し、これを最適化することができます。
反復回数に応じて、パフォーマンスの違いはほとんど無視されます。
ただし、ループの外側に配置すると、ループの本体がよりきれいに見えるという点で私は同意します。
無効な数値がある場合に終了するのではなく、処理を続行する可能性がある場合は、コードをループ内に配置する必要があります。
内側にある場合、外側に1回だけではなく、try / catch構造のオーバーヘッドをN回取得します。
Try / Catch構造体が呼び出されるたびに、メソッドの実行にオーバーヘッドが追加されます。ほんの少しのメモリ&amp;構造を処理するために必要なプロセッサティック。ループを100回実行している場合、仮にコストがtry / catch呼び出しごとに1ティックであり、ループ内でTry / Catchを使用すると、100ティックのコストがかかります。ループ外。
例外の全体のポイントは、最初のスタイルを奨励することです。エラー処理をすべての可能なエラーサイトですぐにではなく、1回統合して処理します。
中に入れます。処理を続けるか(必要な場合)、myStringの値と不良値を含む配列のインデックスをクライアントに通知する有用な例外をスローできます。 NumberFormatExceptionはすでに悪い値を教えてくれると思いますが、原則は、スローする例外にすべての有用なデータを配置することです。プログラムのこの時点でデバッガで何が面白いかを考えてください。
検討:
try {
// parse
} catch (NumberFormatException nfe){
throw new RuntimeException("Could not parse as a Float: [" + myString +
"] found at index: " + i, nfe);
}
必要なときに、可能な限り多くの情報を含むこのような例外を本当に感謝します。
例外処理を配置する場所の一般的な問題を調べる際に、競合する2つの考慮事項について、独自の 0.02c
を追加します。
-
&quot;ワイドラー&quot;
try-catch
ブロックの責任(つまり、あなたの場合はループ外)は、後でコードを変更するときに、既存のによって処理される行を誤って追加する可能性があることを意味しますcatch
ブロック;おそらく意図せず。あなたの場合、NumberFormatException
を明示的にキャッチしているため、これはあまり起こりません。
-
「狭い」」
try-catch
ブロックの責任であるほど、リファクタリングはより困難になります。特に(あなたの場合のように)「非ローカル」を実行している場合catch
ブロック内からの命令(return null
ステートメント)。
それは障害処理に依存します。エラー要素をスキップしたいだけなら、内部で試してください:
for(int i = 0; i < max; i++) {
String myString = ...;
try {
float myNum = Float.parseFloat(myString);
myFloats[i] = myNum;
} catch (NumberFormatException ex) {
--i;
}
}
その他の場合は、屋外で試してみることをお勧めします。コードはより読みやすく、よりクリーンです。 nullを返す場合は、代わりにエラーの場合にIllegalArgumentExceptionをスローする方が良いでしょう。
$ 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つの側面は、すべてのtry-catchがスタックに何らかの影響を与えるという事実です。
メソッド&quot; outer()&quot;の場合メソッド&quot; inner()&quot;を呼び出します(それ自体を再帰的に呼び出す場合があります)、メソッド&quot; outer()&quot;でtry-catchを見つけてください。可能なら。単純な「スタッククラッシュ」パフォーマンスクラスで使用する例は、try-catchがinnerメソッドにある場合は約6,400フレームで失敗し、outerメソッドにある場合は約11,600フレームで失敗します。
実世界では、Compositeパターンを使用していて、大きく複雑なネスト構造がある場合、これが問題になる可能性があります。
各反復で例外をキャッチする場合、または反復で例外がスローされ、反復内のすべての例外をキャッチする場合は、ループ内にtry ... catchを配置します。例外が発生してもループは中断されず、ループ全体の各反復ですべての例外をキャッチできます。
ループを中断し、スローされるたびに例外を調べる場合は、ループからtry ... catchを使用します。これにより、ループが解除され、catch(存在する場合)の後にステートメントが実行されます。
それはすべてあなたのニーズに依存します。展開中にループ内でtry ... catchを使用することをお勧めします。例外が発生した場合、結果はあいまいではなく、ループは完全に壊れて実行されません。