webentwicklung-frage-antwort-db.com.de

Variablen innerhalb oder außerhalb einer Schleife deklarieren

Warum funktioniert das Folgende gut?

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

Aber dieses soll gefährlich/falsch sein:

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

Ist es notwendig, Variablen außerhalb der Schleife zu deklarieren?

207
Harry Joy

Der Umfang der lokalen Variablen sollte immer so klein wie möglich sein.

In Ihrem Beispiel gehe ich davon aus, dass strnot außerhalb der while-Schleife verwendet wird, andernfalls würden Sie die Frage nicht stellen, da das Deklarieren innerhalb der while-Schleife keine Option wäre, da sie nicht kompiliert würde.

Da strnot außerhalb der Schleife verwendet wird, ist der kleinstmögliche Bereich für strwithin der while-Schleife.

Die Antwort ist also nachdrücklich, dass str unbedingt innerhalb der while-Schleife deklariert werden sollte. Kein Wenn, Nein und Nein.

Der einzige Fall, gegen den gegen diese Regel verstoßen werden könnte, ist, wenn es aus irgendeinem Grund von entscheidender Bedeutung ist, dass jeder Taktzyklus aus dem Code herausgedrückt wird. In diesem Fall möchten Sie vielleicht erwägen, etwas in einem äußeren Gültigkeitsbereich zu instanziieren und stattdessen erneut zu verwenden Bei jeder Iteration eines inneren Bereichs erneut instanziieren. Dies gilt jedoch nicht für Ihr Beispiel, da die Zeichenfolgen in Java nicht veränderbar sind: Eine neue Instanz von str wird immer am Anfang Ihrer Schleife erstellt und muss am Ende, also dort, weggeworfen werden gibt es keine möglichkeit dort zu optimieren.

EDIT: (füge meinen Kommentar unten in der Antwort ein)

In jedem Fall ist der richtige Weg, um den Code richtig zu schreiben, eine Leistungsanforderung für Ihr Produkt festzulegen, Ihr Endprodukt an dieser Anforderung zu messen, und wenn es nicht den Anforderungen entspricht, dann gehen Sie zur Optimierung. Und was normalerweise passiert, ist, dass Sie Möglichkeiten finden, einige formale algorithmische Optimierungen von Nice an nur wenigen Stellen bereitzustellen, wodurch unser Programm seine Leistungsanforderungen erfüllt, anstatt dass Sie Ihre gesamte Codebasis durchlaufen und Tweak durchgehen müssen um hier und da Taktzyklen zu quetschen.

265
Mike Nakis

Ich habe den Bytecode dieser beiden (ähnlichen) Beispiele verglichen:

Betrachten wir 1. Beispiel :

package inside;

public class Test {
    public static void main(String[] args) {
        while(true){
            String str = String.valueOf(System.currentTimeMillis());
            System.out.println(str);
        }
    }
}

nach javac Test.Java, javap -c Test erhalten Sie:

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

}

Betrachten wir 2. Beispiel :

package outside;

public class Test {
    public static void main(String[] args) {
        String str;
        while(true){
            str =  String.valueOf(System.currentTimeMillis());
            System.out.println(str);
        }
    }
}

nach javac Test.Java, javap -c Test erhalten Sie:

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

}

Die Beobachtungen zeigen, dass zwischen diesen beiden Beispielen kein Unterschied besteht. Es ist das Ergebnis von JVM-Spezifikationen ...

Im Namen der besten Codierungspraxis wird jedoch empfohlen, die Variable im kleinstmöglichen Bereich zu deklarieren (in diesem Beispiel befindet sie sich innerhalb der Schleife, da nur hier die Variable verwendet wird).

272
PrimosK

Deklarieren von Objekten im kleinster Gültigkeitsbereich Verbesserung Lesbarkeit .

Die Leistung spielt für heutige Compiler keine Rolle (in diesem Szenario).
Unter dem Gesichtspunkt der Wartung ist die Option 2nd besser.
Deklarieren und initialisieren Sie Variablen an derselben Stelle, und zwar im engsten möglichen Bereich.

Als Donald Ervin Knuth erzählte:

"Wir sollten kleine Wirkungsgrade vergessen, sagen wir 97% der Zeit: Vorzeitige Optimierung ist die Wurzel allen Übels"

d) Situation, in der ein Programmierer die Überlegungen der Leistung auf den Entwurfeines Code-Teils beeinflusst. Dies kann zu einem Design führen, das nicht so sauber ist, als es oder-Code wäre, der falsch ist, weil der Code kompliziert ist durch die Optimierung und der Programmierer wird von Optimierung abgelenkt.

23
Chandra Sekhar

wenn Sie str auch außerhalb von looop verwenden möchten; erkläre es draußen. Ansonsten ist die 2. Version in Ordnung.

12
Azodious

Bitte überspringen Sie die aktualisierte Antwort ...

Für diejenigen, die Wert auf Leistung legen, nehmen Sie System.out heraus und beschränken Sie die Schleife auf 1 Byte. Bei Verwendung von double (Test 1/2) und String (3/4) werden die verstrichenen Zeiten in Millisekunden unter Windows 7 Professional 64 Bit und JDK-1.7.0_21 angegeben. Bytecodes (auch unten für test1 und test2 angegeben) sind nicht gleich. Ich war zu faul, um mit veränderlichen und relativ komplexen Objekten zu testen.

doppelt

Test1 nahm: 2710 ms

Test2 nahm: 2790 ms

String (Ersetzen Sie einfach double durch string in den Tests)

Test3 dauerte: 1200 ms

Test4 dauerte: 3000 ms

Kompilieren und Abrufen von Bytecode

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
}

AKTUALISIERTE ANTWORT

Es ist wirklich nicht einfach, die Leistung mit allen JVM-Optimierungen zu vergleichen. Es ist jedoch etwas möglich. Besserer Test und detailliertere Ergebnisse in Google Caliper

  1. Einige Details im Blog: Solltest du eine Variable innerhalb einer Schleife oder vor der Schleife deklarieren?
  2. GitHub-Repository: https://github.com/gunduru/jvdt
  3. Testergebnisse für Double Case und 100M-Schleife (und alle JVM-Details): https://microbenchmarks.appspot.com/runs/b1cef8d1-0e2c-4120-be61-a99faff625b4

DeclaredBefore 1,759.209 DeclaredInside 2,242.308

  • DeklariertBevor 1.759.209 ns
  • DeklariertIn 2.242.308 ns

Partial Test Code für doppelte Deklaration

Dies ist nicht identisch mit dem obigen Code. Wenn Sie nur eine Dummy-Loop-JVM codieren, wird diese übersprungen, sodass Sie zumindest etwas zuweisen und zurückgeben müssen. Dies wird auch in der Caliper-Dokumentation empfohlen.

@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;
}

Zusammenfassung: DeklariertBefore bedeutet bessere Leistung - wirklich winzig - und widerspricht dem Prinzip des kleinsten Umfangs. JVM sollte dies eigentlich für Sie tun

8
Onur Günduru

Je kleiner der Bereich, in dem die Variable sichtbar ist, desto besser.

7
Jan Zyka

Eine Lösung für dieses Problem könnte darin bestehen, einen variablen Bereich bereitzustellen, der die while-Schleife einkapselt:

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

Sie werden automatisch abgesetzt, wenn der äußere Bereich endet.

7
Morten Madsen

Wenn Sie str nach der while-Schleife nicht benötigen (bereichsbezogen), dann ist die zweite Bedingung d. 

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

ist besser, da Sie nur dann ein Objekt auf dem Stapel definieren, wenn condition wahr ist. Das heißt benutze es wenn du es brauchst

6
Cratylus

Ich denke, der beste Weg zur Beantwortung Ihrer Frage wäre der folgende Beitrag:

Unterschied zwischen der Deklaration von Variablen vor oder in der Schleife?

Nach meinem Verständnis wäre diese Sache sprachabhängig. IIRC Java optimiert dies, so dass es keinen Unterschied gibt, aber JavaScript (zum Beispiel) wird die gesamte Speicherzuweisung jedes Mal in der Schleife vornehmen. Insbesondere in Java denke ich, dass die zweite schneller ist, wenn die Profilerstellung abgeschlossen ist.

4
Naveen Goyal

Durch das Deklarieren von String str außerhalb der Wile-Schleife kann auf diese innerhalb und außerhalb der while-Schleife verwiesen werden. Durch das Deklarieren von String str innerhalb der while-Schleife kann only innerhalb der while-Schleife referenziert werden. 

3
Jay Tomten

Wie viele Leute darauf hingewiesen haben,

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

istNICHTbesser als dieses:

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

Deklarieren Sie also keine Variablen außerhalb ihres Gültigkeitsbereichs, wenn Sie sie nicht wiederverwenden ...

2
Pavan

Variablen sollten so nah am Verwendungsort wie möglich deklariert werden.

Es macht RAII (Ressourcenerfassung ist Initialisierung) einfacher.

Es hält den Umfang der Variablen eng. Dadurch kann der Optimierer besser arbeiten.

2
vikiiii

Laut Google Android Development Guide sollte der variable Umfang begrenzt sein. Bitte überprüfen Sie diesen Link:

Variabler Umfang begrenzen

2
James Jithin

Durch die Deklaration innerhalb der Schleife wird der Gültigkeitsbereich der jeweiligen Variablen begrenzt. Es hängt alles von der Anforderung des Projekts an den Umfang der Variablen ab.

1
ab02

Die oben genannte Frage ist wahrlich ein Programmierproblem. Wie möchten Sie Ihren Code programmieren? Wo müssen Sie auf die 'STR' zugreifen? Es ist nicht sinnvoll, eine Variable zu deklarieren, die lokal als globale Variable verwendet wird. Grundlagen der Programmierung glaube ich.

1

Ich denke, dass die Größe des Objekts ebenfalls von Bedeutung ist. In einem meiner Projekte hatten wir ein großes zweidimensionales Array deklariert und initialisiert, das die Anwendung dazu veranlasste, eine Out-of-Memory-Ausnahme auszulösen deklaration stattdessen aus der Schleife und löschte das Array zu Beginn jeder Iteration.

0
Sanjit

Die Variable str ist verfügbar und reserviert, auch wenn sie unterhalb des Codes ausgeführt wird.

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

Die Variable str ist nicht verfügbar und auch der Speicher wird freigegeben, der für die Variable str im folgenden Code zugewiesen wurde.

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

Wenn wir dem zweiten folgen, wird dies sicherlich den Systemspeicher reduzieren und die Leistung steigern.

0

Diese beiden Beispiele ergeben dasselbe. Im ersten Abschnitt können Sie jedoch die Variable str außerhalb der while-Schleife verwenden. der zweite ist nicht.

0
olyanren