限られた範囲がベスト
2 番目のオプションを使用します。
for ( ... ) {
String s = ...;
}
スコープはパフォーマンスに影響しません
(JDK のツールを使用して) それぞれからコンパイルされたコードを逆アセンブルするjavap
と、どちらの場合もループがまったく同じ JVM 命令にコンパイルされることがわかります。また、 Brian R. Bondy の「オプション #3」はオプション #1 と同じであることに注意してください。より厳密なスコープを使用する場合、スタックから追加または削除されるものは何もなく、どちらの場合もスタックで同じデータが使用されます。
時期尚早の初期化を避ける
2 つのケースの唯一の違いは、最初の例では変数s
が不必要に初期化されることです。これは、変数宣言の場所とは別の問題です。これにより、2 つの無駄な命令が追加されます (文字列定数をロードし、それをスタック フレーム スロットに格納するため)。優れた静的分析ツールは、 に割り当てた値を読み取っていないことを警告しs
、優れた JIT コンパイラはおそらく実行時にそれを省略します。
空の宣言 (つまりString s;
) を使用するだけでこれを修正できますが、これは悪い習慣と見なされ、以下で説明する別の副作用があります。
null
多くの場合、変数が初期化されずに読み取られたというコンパイラ エラーを単に黙らせるために、偽の値のようなものが変数に割り当てられます。このエラーは、変数のスコープが大きすぎて、有効な値を受け取る前に宣言されていることを示唆している可能性があります。空の宣言では、すべてのコード パスを考慮する必要があります。偽の値を割り当てて、この貴重な警告を無視しないでください。
スタック スロットの節約
前述のように、JVM 命令はどちらの場合も同じですが、JVM レベルで、可能な限り限られたスコープを使用するのが最適な微妙な副作用があります。これは、メソッドの「ローカル変数テーブル」に表示されます。不必要に大きなスコープで宣言された変数を使用して、複数のループがあるとどうなるかを考えてみましょう。
void x(String[] strings, Integer[] integers) {
String s;
for (int i = 0; i < strings.length; ++i) {
s = strings[0];
...
}
Integer n;
for (int i = 0; i < integers.length; ++i) {
n = integers[i];
...
}
}
変数s
とn
は、それぞれのループ内で宣言できますが、そうではないため、コンパイラはスタック フレームで 2 つの「スロット」を使用します。それらがループ内で宣言されている場合、コンパイラは同じスロットを再利用して、スタック フレームを小さくすることができます。
本当に重要なこと
ただし、これらの問題のほとんどは重要ではありません。優れた JIT コンパイラは、無駄に代入している初期値を読み取ることができないことを認識し、代入を最適化して取り除きます。あちらこちらにスロットを保存しても、アプリケーションが成功したり壊れたりすることはありません。
重要なことは、コードを読みやすく、保守しやすいものにすることです。その点では、限定されたスコープを使用する方が明らかに優れています。変数のスコープが小さいほど、それがどのように使用され、コードへの変更がどのような影響を与えるかを理解しやすくなります。