56

if ブロックとは対照的に、 Java でtry/catchブロックを使用するためのオーバーヘッドはありますか?

たとえば、文字列の「安全なトリム」メソッドの次の 2 つの単純な実装を取り上げます。

public String tryTrim(String raw) {
    try {
        return raw.trim();
    } catch (Exception e) {
    }
    return null;
}

public String ifTrim(String raw) {
    if (raw == null) {
        return null;
    }
    return raw.trim();
}

raw入力がめったにない場合null、2 つの方法の間にパフォーマンスの違いはありますか?

さらに、コードのレイアウトを簡素化するためのアプローチを使用することは、特にコードを 1 つの try/catch ブロックで囲むことによってまれなエラー条件をチェックする多数のif ブロックを回避できる場合に、適切なプログラミング パターンですか?tryTrim()

たとえばN parameters、 を使用するメソッドを持つことはよくあることです。これはM <= N、その開始近くでそれらを使用し、そのようなパラメータが「無効」(たとえば、null または空の文字列) である場合、残りの部分に影響を与えることなく、迅速かつ確定的に失敗します。コード。

k * M そのような場合、 if ブロック(ここでkはパラメーターごとのチェックの平均数、たとえばk = 2null または空の文字列)を記述する代わりに、 try/catchブロックを使用するとコードが大幅に短縮され、1 ~ 2 行のコメントを使用できます。 「型にはまらない」ロジックに明示的に注意してください。

このようなパターンは、特にエラー状態がめったに発生しない場合にメソッドを高速化し、プログラムの安全性を損なうことなく高速化します (たとえば、null または空の値を持つ文字列処理メソッドのように、エラー状態が「正常」であると仮定すると)。存在することはめったにありませんが、許容されます)。

4

5 に答える 5

65

パフォーマンスのオーバーヘッドについて質問されていることは承知していますが、実際にはtry/catchif同じ意味で使用しないでください。

try/catchは、通常のプログラム フローではなく、制御できない問題が発生した場合に使用します。たとえば、ファイルに書き込もうとして、ファイル システムがいっぱいですか? その状況は通常、try/で処理する必要がありcatchます。

ifステートメントは、通常のフローおよび通常のエラー チェックである必要があります。たとえば、ユーザーが必須入力フィールドの入力に失敗したとしますか? /ifではなく、そのために使用します。trycatch

あなたのコード例は、 /ifではなくステートメントがある正しいアプローチを強く示唆しているように思えます。trycatch

あなたの質問に答えるために、一般的にtry/catchよりもオーバーヘッドが多いと思いますif。確実に知るには、Java プロファイラーを入手して、関心のある特定のコードを見つけてください。状況によって答えが変わる可能性があります。

于 2011-12-24T00:18:07.233 に答える
34

この質問はほとんど「死ぬほど答えられた」ものですが、役に立つポイントが他にもいくつかあると思います。

  • try / catch非例外的な制御フローに使用するのは悪いスタイルです (Java では)。(「非例外的」が何を意味するかについてはよく議論されますが、それは別のトピックです。)

  • それが悪いスタイルである理由の1 つは、通常の制御フロー ステートメントよりも桁違いにコストがかかるtry / catchことです1。実際の違いはプログラムとプラットフォームによって異なりますが、1000 倍以上の費用がかかると思います。特に、例外オブジェクトを作成すると、スタック トレースがキャプチャされ、スタック上の各フレームに関する情報が検索およびコピーされます。スタックが深ければ深いほど、コピーする必要があるものは多くなります。

  • それが悪いスタイルであるもう 1 つの理由は、コードが読みにくいことです。

1 - 最近のバージョンの Java の JIT は例外処理を最適化して、場合によってはオーバーヘッドを大幅に削減できます。ただし、これらの最適化はデフォルトでは有効になっていません。

例を書いた方法にも問題があります。

  • Exception他のチェックされていない例外を誤ってキャッチする可能性があるため、キャッチは非常に悪い習慣です。たとえば、呼び出しの周りでそれを行うと、潜在的なs ...raw.substring(1)もキャッチされ、バグが隠されます。StringIndexOutOfBoundsException

  • あなたの例がやろうとしていることは、(おそらく)null文字列を扱う際の練習不足の結果です。一般的な原則として、文字列の使用を最小限に抑え、null(意図的な) 拡散を制限するように努める必要があります。可能であれば、null「値なし」を意味するのではなく、空の文字列を使用してください。また、文字列を渡したり返したりする必要がある場合はnull、メソッドの javadoc で明確に文書化してください。メソッドが呼び出されるnullべきではないときに呼び出される場合...それはバグです。例外をスローさせます。(この例では) を返すことによってバグを補おうとしないでくださいnull


私の質問は、null値だけでなく、より一般的なものでした。

...そして、私の答えのポイントのほとんどは、null価値観に関するものではありません!

ただし、ときどき null 値や、例外を生成する可能性のあるその他の値を許可し、それらを無視したい場合が多いことに注意してください。これは、たとえば、どこかからキー/ペアの値を読み取り、それらを上記の tryTrim() のようなメソッドに渡す場合です。

はい、null値が期待される状況があり、それらに対処する必要があります。

しかし、私tryTrim()が行っていることは (通常) に対処するための間違った方法であると主張しnullます。次の 3 つのコードを比較します。

// Version 1
String param = httpRequest.getParameter("foo");
String trimmed = tryTrim(param);
if (trimmed == null) {
    // deal with case of 'foo' parameter absent
} else {
    // deal with case of 'foo' parameter present
}

// Version 2
String param = httpRequest.getParameter("foo");
if (param == null) {
    // deal with case of 'foo' parameter absent
} else {
    String trimmed = param.trim();
    // deal with case of 'foo' parameter present
}

// Version 3
String param = httpRequest.getParameter("foo");
if (param == null) {
    // treat missing and empty parameters the same
    param = "";
}
String trimmed = param.trim();

最終的にはnull、通常の文字列とは異なる方法で処理する必要があり、通常はできるだけ早くこれを行うことをお勧めします。nullがその原点からさらに伝播できるようになればなるほど、プログラマーは値が可能性であることを忘れnullnull 以外の値を想定するバグのあるコードを書く可能性が高くなります。また、HTTP 要求パラメーターが欠落している可能性があること (つまりparam == null) を忘れることは、これが発生する典型的なケースです。

tryTrim()それが本質的に悪いと言っているのではありません。しかし、このようなメソッドを作成する必要性を感じているという事実は、おそらくnull 処理が理想的ではないことを示しています。

最後に、API で「値がない」ことをモデル化する方法は他にもあります。これらには以下が含まれます:

  • セッターとコンストラクターで予期nullしないことをテストし、例外をスローするか、別の値に置き換えます。
  • メソッドを追加し、結果が の場合にis<Field>Set例外をスローします。get<Field>null
  • Null オブジェクト パターンを使用します。
  • null文字列、コレクション、または配列の代わりに、空の文字列、空のコレクション、長さゼロの配列などを使用します1
  • を使用してOptionalいます。

1 - 「nullity」を表す型のインスタンスが 1 つだけであるとは限らないため、これは Null オブジェクト パターンとは異なります。しかし、この 2 つのアイデアを組み合わせることができます。

于 2011-12-24T02:20:55.910 に答える
11

2 番目のバージョンを使用します。他の代替手段が利用可能な場合は、制御フローに例外を使用しないでください。それは目的ではないためです。例外は例外的な状況のためのものです。

トピックについている間は、Exceptionここでキャッチしないでください。特に飲み込まないでください。あなたの場合、あなたはNullPointerException. 何かをキャッチする場合、それをキャッチします (ただし、段落 1 に戻ります。これは行わないでください)。あなたが捕まえる(そして飲み込む!)Exceptionとき、あなたは「何がうまくいかなくても、私はそれを処理できます。それが何であるかは気にしません」と言っています。あなたのプログラムは取り返しのつかない状態にあるかもしれません! 対処する準備ができているものだけをキャッチし、他のすべてをそれを処理できるレイヤーに伝播させます。そのレイヤーが最上位レイヤーであり、例外をログに記録してからイジェクト スイッチを押すだけであっても同様です。

于 2011-12-24T00:18:20.957 に答える
5

それ以外の場合、例外のスローとキャッチは高速ですが (ただし、ifおそらく an の方が高速です)、例外のスタック トレースの作成が遅くなります。これは、現在のスタックのすべてをウォークスルーする必要があるためです。(一般に、制御フローに例外を使用するのは良くありませんが、それが本当に必要で、例外が高速でなければならない場合は、Throwable.fillInStackTrace()メソッドをオーバーライドしてスタック トレースの構築をスキップするか、1 つの例外インスタンスを保存して、代わりに繰り返しスローすることができます。常に新しい例外インスタンスを作成します。)

于 2011-12-24T00:18:53.777 に答える
-3

オーバーヘッドに関する限り、自分でテストできます。

public class Overhead {

public static void main(String[] args) {
    String testString = "";

    long startTimeTry = System.nanoTime();
    tryTrim(testString);
    long estimatedTimeTry = System.nanoTime() - startTimeTry;

    long startTimeIf = System.nanoTime();
    ifTrim(testString);
    long estimatedTimeIf = System.nanoTime() - startTimeIf;

    System.out.println("Try time:" + estimatedTimeTry);
    System.out.println("If time:" + estimatedTimeIf);

}

public static String tryTrim(String raw) {
    try {
        return raw.trim();
    } catch (Exception e) {
    }
    return null;
}

public static String ifTrim(String raw) {
    if (raw == null) {
        return null;
    }
    return raw.trim();
}

}

私が得た数字は次のとおりです。

Try time:8102
If time:1956

どのようなスタイルかについては、まったく別の問題です。if ステートメントは非常に自然に見えますが、try は複数の理由で非常に奇妙に見えます: - NULL 値をチェックしているにもかかわらず Exception をキャッチしました。何か「例外的な」ことが起こると予想していますか (そうでなければ NullException をキャッチします)? - その例外を見つけました。それを報告しますか、それとも飲み込みますか? 等等等

編集: これが無効なテストである理由については、私のコメントを参照してください。tryTrim と ifTrim を交換するだけで、(私のマシンでは) 突然次の結果が得られます。

Try time:2043
If time:6810

このすべてを説明し始める代わりに、最初にこれを読んでください。Cliff にはトピック全体に関する素晴らしいスライドもありますが、現時点ではリンクが見つかりません。

Hotspot で例外処理がどのように機能するかを知っているので、正しいテストでは例外なしの try/catch がベースライン パフォーマンスになると確信していますが (オーバーヘッドがまったくないため)、JIT は Nullpointer チェックとそれに続くメソッド呼び出し (明示的なチェックはありませんが、オブジェクトが null の場合は hw 例外をキャッチします) の場合、同じ結果が得られます。また、忘れてはならないのは、簡単に予測できる 1 つの CPU サイクルの違いについて話しているということです。トリム コールには、その 100 万倍の費用がかかります。

于 2011-12-24T00:33:12.623 に答える