web-dev-qa-db-ja.com

ループの内側または外側で変数を宣言する

なぜ次のように動作しますか?

String str;
while (condition) {
    str = calculateStr();
    .....
}

しかし、これは危険/不正であると言われています:

while (condition) {
    String str = calculateStr();
    .....
}

ループ外で変数を宣言する必要がありますか?

212
Harry Joy

ローカル変数のスコープは常に可能な限り小さくする必要があります。

あなたの例では、strwhileループの外側でnot使用されていると仮定します。そうでない場合は、whileループ内で宣言するとコンパイルされないため、オプションではありません。

したがって、strnotループ外で使用されるため、strの最小スコープはwithinwhileループ。

したがって、答えは、強調的にstrをwhileループ内で絶対に宣言する必要があるということです。 ifs、nos、no butsはありません。

このルールに違反する可能性がある唯一のケースは、何らかの理由で、すべてのクロックサイクルをコードから絞り出さなければならないことが非常に重要な場合です。その場合、外部スコープで何かをインスタンス化し、代わりに再利用することを検討することができます内部スコープのすべての反復でそれを再インスタンス化します。ただし、Javaの文字列は不変であるため、これは例には適用されません。ループの最初に常にstrの新しいインスタンスが作成され、ループの最後に破棄する必要があるため、そこで最適化する可能性はありません。

編集:(答えに以下のコメントを挿入)

いずれにせよ、物事を行う正しい方法は、すべてのコードを適切に記述し、製品のパフォーマンス要件を確立し、この要件に対して最終製品を測定し、それを満たさない場合は、最適化することです。そして、通常は最終的に何が起こるかは、コードベース全体を調整して物事をハッキングする代わりに、プログラムがパフォーマンス要件を満たすようにするために、ほんの数か所でいくつかのニースで正式なアルゴリズム最適化を提供する方法を見つけることですあちこちでクロックサイクルを圧縮するために。

270
Mike Nakis

これら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仕様の結果です...

しかし、ベストコーディングプラクティスの名前では、可能な限り小さいスコープで変数を宣言することをお勧めします(この例では、変数が使用される唯一の場所であるため、ループ内にあります)。

281
PrimosK

最も小さいスコープでオブジェクトを宣言すると、読みやすさが向上します。

今日のコンパイラーではパフォーマンスは重要ではありません。(このシナリオでは)
メンテナンスの観点からは、2ndオプションの方が優れています。
できるだけ狭い範囲で、同じ場所で変数を宣言して初期化します。

Donald Ervin Knuthが言ったように:

「小さな効率を忘れて、約97%の時間を言う必要があります:早すぎる最適化はすべての悪の根源です」

すなわち、プログラマーがパフォーマンスの考慮をコードの一部のdesignに影響させる状況。これにより、それほどクリーンではないであった可能性のある設計になりますまたはコードは、 複雑optimizationであり、プログラマーはoptimizing

23
Chandra Sekhar

ループ外でstrも使用したい場合;外で宣言します。それ以外の場合は、2番目のバージョンで問題ありません。

12
Azodious

更新された答えにスキップしてください...

パフォーマンスを重視する場合は、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 でのより良いテストと詳細な結果

  1. ブログの詳細: ループ内またはループ前に変数を宣言する必要がありますか?
  2. GitHubリポジトリ: https://github.com/gunduru/jvdt
  3. ダブルケースと100Mループのテスト結果(およびすべてのJVM詳細): https://microbenchmarks.appspot.com/runs/b1cef8d1-0e2c-4120-be61-a99faff625b4

DeclaredBefore 1,759.209 DeclaredInside 2,242.308

  • 宣言される前1,759.209 ns
  • 宣言された内部2,242.308 ns

二重宣言の部分テストコード

これは上記のコードと同一ではありません。ダミーループをコーディングするだけの場合、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は実際にこれを行うべきです

8
Onur Günduru

内部では、変数のスコープが小さいほど、より良く見えます。

7
Jan Zyka

この問題の1つの解決策は、whileループをカプセル化する変数スコープを提供することです。

{
  // all tmp loop variables here ....
  // ....
  String str;
  while(condition){
      str = calculateStr();
      .....
  }
}

外部スコープが終了すると、それらは自動的に逆参照されます。

7
Morten Madsen

Whileループ(スコープ関連)の後にstrを使用する必要がない場合、2番目の条件、つまり.

  while(condition){
        String str = calculateStr();
        .....
    }

conditionがtrueの場合にのみスタック上でオブジェクトを定義すると、より良い結果が得られます。つまりそれを使用してください必要な場合

6
Cratylus

あなたの質問に答えるのに最適なリソースは、次の投稿でしょう。

ループの前またはループ内の変数の宣言の違い?

私の理解によると、このことは言語に依存します。 IIRC Javaはこれを最適化するので、違いはありませんが、たとえばJavaScriptはループ内で毎回メモリ全体を割り当てます。Javaでは特に、プロファイリング済み。

4
Naveen Goyal

Wileループの外側でString strを宣言すると、whileループの内側および外側で参照できます。 whileループ内で文字列strを宣言すると、whileループ内でonlyを参照できます。

3
Jay Tomten

多くの人が指摘しているように、

String str;
while(condition){
    str = calculateStr();
    .....
}

NOTこれよりも優れています:

while(condition){
    String str = calculateStr();
    .....
}

したがって、変数を再利用しない場合は、スコープ外で変数を宣言しないでください...

2
Pavan

変数は、使用される場所のできるだけ近くで宣言する必要があります。

RAII (Resource Acquisition Is Initialization) が簡単になります。

変数のスコープを厳しくします。これにより、オプティマイザーの動作が改善されます。

2
vikiiii

Google Android開発ガイドによると、変数のスコープは制限されるべきです。このリンクを確認してください:

変数スコープの制限

2
James Jithin

ループ内で宣言すると、それぞれの変数のスコープが制限されます。それはすべて、変数のスコープに関するプロジェクトの要件に依存します。

1
ab02

本当に、上記の質問はプログラミングの問題です。コードをどのようにプログラムしますか? 「STR」にアクセスするにはどこが必要ですか?グローバル変数としてローカルで使用される変数を宣言することはありません。プログラミングの基礎。

1

オブジェクトのサイズも重要だと思います。私のプロジェクトの1つで、アプリケーションがメモリ不足の例外をスローするような大きな2次元配列を宣言して初期化しました。代わりに宣言をループ外に移動し、すべての反復の開始時に配列をクリアしました。

0
Sanjit

str変数は、コードの下で実行された後でも使用可能になり、メモリ内の一部のスペースを予約します。

 String str;
    while(condition){
        str = calculateStr();
        .....
    }

str変数は使用できません。また、以下のコードでstr変数に割り当てられたメモリも解放されます。

while(condition){
    String str = calculateStr();
    .....
}

2番目の手順を実行した場合、システムメモリが削減され、パフォーマンスが向上します。

0

これら2つの例は同じ結果になります。ただし、最初の方法では、whileループの外側でstr変数を使用できます。 2番目はそうではありません。

0
olyanren