なぜ次のように動作しますか?
String str;
while (condition) {
str = calculateStr();
.....
}
しかし、これは危険/不正であると言われています:
while (condition) {
String str = calculateStr();
.....
}
ループ外で変数を宣言する必要がありますか?
あなたの例では、str
はwhile
ループの外側でnot使用されていると仮定します。そうでない場合は、while
ループ内で宣言するとコンパイルされないため、オプションではありません。
したがって、str
はnotループ外で使用されるため、str
の最小スコープはwithinwhileループ。
したがって、答えは、強調的に、str
をwhileループ内で絶対に宣言する必要があるということです。 ifs、nos、no butsはありません。
このルールに違反する可能性がある唯一のケースは、何らかの理由で、すべてのクロックサイクルをコードから絞り出さなければならないことが非常に重要な場合です。その場合、外部スコープで何かをインスタンス化し、代わりに再利用することを検討することができます内部スコープのすべての反復でそれを再インスタンス化します。ただし、Javaの文字列は不変であるため、これは例には適用されません。ループの最初に常にstrの新しいインスタンスが作成され、ループの最後に破棄する必要があるため、そこで最適化する可能性はありません。
編集:(答えに以下のコメントを挿入)
いずれにせよ、物事を行う正しい方法は、すべてのコードを適切に記述し、製品のパフォーマンス要件を確立し、この要件に対して最終製品を測定し、それを満たさない場合は、最適化することです。そして、通常は最終的に何が起こるかは、コードベース全体を調整して物事をハッキングする代わりに、プログラムがパフォーマンス要件を満たすようにするために、ほんの数か所でいくつかのニースで正式なアルゴリズム最適化を提供する方法を見つけることですあちこちでクロックサイクルを圧縮するために。
これら2つの(同様の)例のバイトコードを比較しました。
1。例を見てみましょう。
package inside;
public class Test {
public static void main(String[] args) {
while(true){
String str = String.valueOf(System.currentTimeMillis());
System.out.println(str);
}
}
}
javac Test.Java
の後、javap -c Test
が得られます:
public class inside.Test extends Java.lang.Object{
public inside.Test();
Code:
0: aload_0
1: invokespecial #1; //Method Java/lang/Object."<init>":()V
4: return
public static void main(Java.lang.String[]);
Code:
0: invokestatic #2; //Method Java/lang/System.currentTimeMillis:()J
3: invokestatic #3; //Method Java/lang/String.valueOf:(J)Ljava/lang/String;
6: astore_1
7: getstatic #4; //Field Java/lang/System.out:Ljava/io/PrintStream;
10: aload_1
11: invokevirtual #5; //Method Java/io/PrintStream.println:(Ljava/lang/String;)V
14: goto 0
}
2。例を見てみましょう。
package outside;
public class Test {
public static void main(String[] args) {
String str;
while(true){
str = String.valueOf(System.currentTimeMillis());
System.out.println(str);
}
}
}
javac Test.Java
の後、javap -c Test
が得られます:
public class outside.Test extends Java.lang.Object{
public outside.Test();
Code:
0: aload_0
1: invokespecial #1; //Method Java/lang/Object."<init>":()V
4: return
public static void main(Java.lang.String[]);
Code:
0: invokestatic #2; //Method Java/lang/System.currentTimeMillis:()J
3: invokestatic #3; //Method Java/lang/String.valueOf:(J)Ljava/lang/String;
6: astore_1
7: getstatic #4; //Field Java/lang/System.out:Ljava/io/PrintStream;
10: aload_1
11: invokevirtual #5; //Method Java/io/PrintStream.println:(Ljava/lang/String;)V
14: goto 0
}
これらの2つの例には、差なしがあることが観察結果からわかります。 JVM仕様の結果です...
しかし、ベストコーディングプラクティスの名前では、可能な限り小さいスコープで変数を宣言することをお勧めします(この例では、変数が使用される唯一の場所であるため、ループ内にあります)。
最も小さいスコープでオブジェクトを宣言すると、読みやすさが向上します。
今日のコンパイラーではパフォーマンスは重要ではありません。(このシナリオでは)
メンテナンスの観点からは、2ndオプションの方が優れています。
できるだけ狭い範囲で、同じ場所で変数を宣言して初期化します。
Donald Ervin Knuthが言ったように:
「小さな効率を忘れて、約97%の時間を言う必要があります:早すぎる最適化はすべての悪の根源です」
すなわち、プログラマーがパフォーマンスの考慮をコードの一部のdesignに影響させる状況。これにより、それほどクリーンではないであった可能性のある設計になりますまたはコードは、 複雑はoptimizationであり、プログラマーはoptimizing。
ループ外でstr
も使用したい場合;外で宣言します。それ以外の場合は、2番目のバージョンで問題ありません。
更新された答えにスキップしてください...
パフォーマンスを重視する場合は、System.outを取り出して、ループを1バイトに制限してください。 double(テスト1/2)とString(3/4)を使用して、ミリ秒単位の経過時間をWindows 7 Professional 64ビットとJDK-1.7.0_21で以下に示します。バイトコード(test1とtest2についても以下に示します)は同じではありません。可変で比較的複雑なオブジェクトをテストするのは面倒でした。
double
Test1の所要時間:2710ミリ秒
Test2の所要時間:2790ミリ秒
文字列(テストでは単にdoubleを文字列に置き換えてください)
Test3の所要時間:1200ミリ秒
Test4の所要時間:3000ミリ秒
バイトコードのコンパイルと取得
javac.exe LocalTest1.Java
javap.exe -c LocalTest1 > LocalTest1.bc
public class LocalTest1 {
public static void main(String[] args) throws Exception {
long start = System.currentTimeMillis();
double test;
for (double i = 0; i < 1000000000; i++) {
test = i;
}
long finish = System.currentTimeMillis();
System.out.println("Test1 Took: " + (finish - start) + " msecs");
}
}
public class LocalTest2 {
public static void main(String[] args) throws Exception {
long start = System.currentTimeMillis();
for (double i = 0; i < 1000000000; i++) {
double test = i;
}
long finish = System.currentTimeMillis();
System.out.println("Test1 Took: " + (finish - start) + " msecs");
}
}
Compiled from "LocalTest1.Java"
public class LocalTest1 {
public LocalTest1();
Code:
0: aload_0
1: invokespecial #1 // Method Java/lang/Object."<init>":()V
4: return
public static void main(Java.lang.String[]) throws Java.lang.Exception;
Code:
0: invokestatic #2 // Method Java/lang/System.currentTimeMillis:()J
3: lstore_1
4: dconst_0
5: dstore 5
7: dload 5
9: ldc2_w #3 // double 1.0E9d
12: dcmpg
13: ifge 28
16: dload 5
18: dstore_3
19: dload 5
21: dconst_1
22: dadd
23: dstore 5
25: goto 7
28: invokestatic #2 // Method Java/lang/System.currentTimeMillis:()J
31: lstore 5
33: getstatic #5 // Field Java/lang/System.out:Ljava/io/PrintStream;
36: new #6 // class Java/lang/StringBuilder
39: dup
40: invokespecial #7 // Method Java/lang/StringBuilder."<init>":()V
43: ldc #8 // String Test1 Took:
45: invokevirtual #9 // Method Java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
48: lload 5
50: lload_1
51: lsub
52: invokevirtual #10 // Method Java/lang/StringBuilder.append:(J)Ljava/lang/StringBuilder;
55: ldc #11 // String msecs
57: invokevirtual #9 // Method Java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
60: invokevirtual #12 // Method Java/lang/StringBuilder.toString:()Ljava/lang/String;
63: invokevirtual #13 // Method Java/io/PrintStream.println:(Ljava/lang/String;)V
66: return
}
Compiled from "LocalTest2.Java"
public class LocalTest2 {
public LocalTest2();
Code:
0: aload_0
1: invokespecial #1 // Method Java/lang/Object."<init>":()V
4: return
public static void main(Java.lang.String[]) throws Java.lang.Exception;
Code:
0: invokestatic #2 // Method Java/lang/System.currentTimeMillis:()J
3: lstore_1
4: dconst_0
5: dstore_3
6: dload_3
7: ldc2_w #3 // double 1.0E9d
10: dcmpg
11: ifge 24
14: dload_3
15: dstore 5
17: dload_3
18: dconst_1
19: dadd
20: dstore_3
21: goto 6
24: invokestatic #2 // Method Java/lang/System.currentTimeMillis:()J
27: lstore_3
28: getstatic #5 // Field Java/lang/System.out:Ljava/io/PrintStream;
31: new #6 // class Java/lang/StringBuilder
34: dup
35: invokespecial #7 // Method Java/lang/StringBuilder."<init>":()V
38: ldc #8 // String Test1 Took:
40: invokevirtual #9 // Method Java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
43: lload_3
44: lload_1
45: lsub
46: invokevirtual #10 // Method Java/lang/StringBuilder.append:(J)Ljava/lang/StringBuilder;
49: ldc #11 // String msecs
51: invokevirtual #9 // Method Java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
54: invokevirtual #12 // Method Java/lang/StringBuilder.toString:()Ljava/lang/String;
57: invokevirtual #13 // Method Java/io/PrintStream.println:(Ljava/lang/String;)V
60: return
}
パフォーマンスをすべてのJVM最適化と比較するのは本当に簡単ではありません。ただし、ある程度可能です。 Google Caliper でのより良いテストと詳細な結果
これは上記のコードと同一ではありません。ダミーループをコーディングするだけの場合、JVMはそれをスキップするため、少なくとも何かを割り当てて返す必要があります。これはCaliperのドキュメントでも推奨されています。
@Param int size; // Set automatically by framework, provided in the Main
/**
* Variable is declared inside the loop.
*
* @param reps
* @return
*/
public double timeDeclaredInside(int reps) {
/* Dummy variable needed to workaround smart JVM */
double dummy = 0;
/* Test loop */
for (double i = 0; i <= size; i++) {
/* Declaration and assignment */
double test = i;
/* Dummy assignment to fake JVM */
if(i == size) {
dummy = test;
}
}
return dummy;
}
/**
* Variable is declared before the loop.
*
* @param reps
* @return
*/
public double timeDeclaredBefore(int reps) {
/* Dummy variable needed to workaround smart JVM */
double dummy = 0;
/* Actual test variable */
double test = 0;
/* Test loop */
for (double i = 0; i <= size; i++) {
/* Assignment */
test = i;
/* Not actually needed here, but we need consistent performance results */
if(i == size) {
dummy = test;
}
}
return dummy;
}
要約:declareBeforeはパフォーマンスが優れていることを示しており(本当に小さい)、最小スコープの原則に反しています。 JVMは実際にこれを行うべきです
内部では、変数のスコープが小さいほど、より良く見えます。
この問題の1つの解決策は、whileループをカプセル化する変数スコープを提供することです。
{
// all tmp loop variables here ....
// ....
String str;
while(condition){
str = calculateStr();
.....
}
}
外部スコープが終了すると、それらは自動的に逆参照されます。
Whileループ(スコープ関連)の後にstr
を使用する必要がない場合、2番目の条件、つまり.
while(condition){
String str = calculateStr();
.....
}
condition
がtrueの場合にのみスタック上でオブジェクトを定義すると、より良い結果が得られます。つまりそれを使用してください必要な場合
あなたの質問に答えるのに最適なリソースは、次の投稿でしょう。
私の理解によると、このことは言語に依存します。 IIRC Javaはこれを最適化するので、違いはありませんが、たとえばJavaScriptはループ内で毎回メモリ全体を割り当てます。Javaでは特に、プロファイリング済み。
Wileループの外側でString strを宣言すると、whileループの内側および外側で参照できます。 whileループ内で文字列strを宣言すると、whileループ内でonlyを参照できます。
多くの人が指摘しているように、
String str;
while(condition){
str = calculateStr();
.....
}
NOTこれよりも優れています:
while(condition){
String str = calculateStr();
.....
}
したがって、変数を再利用しない場合は、スコープ外で変数を宣言しないでください...
変数は、使用される場所のできるだけ近くで宣言する必要があります。
RAII (Resource Acquisition Is Initialization) が簡単になります。
変数のスコープを厳しくします。これにより、オプティマイザーの動作が改善されます。
Google Android開発ガイドによると、変数のスコープは制限されるべきです。このリンクを確認してください:
ループ内で宣言すると、それぞれの変数のスコープが制限されます。それはすべて、変数のスコープに関するプロジェクトの要件に依存します。
本当に、上記の質問はプログラミングの問題です。コードをどのようにプログラムしますか? 「STR」にアクセスするにはどこが必要ですか?グローバル変数としてローカルで使用される変数を宣言することはありません。プログラミングの基礎。
オブジェクトのサイズも重要だと思います。私のプロジェクトの1つで、アプリケーションがメモリ不足の例外をスローするような大きな2次元配列を宣言して初期化しました。代わりに宣言をループ外に移動し、すべての反復の開始時に配列をクリアしました。
str
変数は、コードの下で実行された後でも使用可能になり、メモリ内の一部のスペースを予約します。
String str;
while(condition){
str = calculateStr();
.....
}
str
変数は使用できません。また、以下のコードでstr
変数に割り当てられたメモリも解放されます。
while(condition){
String str = calculateStr();
.....
}
2番目の手順を実行した場合、システムメモリが削減され、パフォーマンスが向上します。
これら2つの例は同じ結果になります。ただし、最初の方法では、whileループの外側でstr
変数を使用できます。 2番目はそうではありません。