Declaring variables inside or outside of a loop

前端 未结 20 2080
后悔当初
后悔当初 2020-11-22 01:59

Why does the following work fine?

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

But this one is said to be dangerou

相关标签:
20条回答
  • 2020-11-22 02:36

    The scope of local variables should always be the smallest possible.

    In your example I presume str is not used outside of the while loop, otherwise you would not be asking the question, because declaring it inside the while loop would not be an option, since it would not compile.

    So, since str is not used outside the loop, the smallest possible scope for str is within the while loop.

    So, the answer is emphatically that str absolutely ought to be declared within the while loop. No ifs, no ands, no buts.

    The only case where this rule might be violated is if for some reason it is of vital importance that every clock cycle must be squeezed out of the code, in which case you might want to consider instantiating something in an outer scope and reusing it instead of re-instantiating it on every iteration of an inner scope. However, this does not apply to your example, due to the immutability of strings in java: a new instance of str will always be created in the beginning of your loop and it will have to be thrown away at the end of it, so there is no possibility to optimize there.

    EDIT: (injecting my comment below in the answer)

    In any case, the right way to do things is to write all your code properly, establish a performance requirement for your product, measure your final product against this requirement, and if it does not satisfy it, then go optimize things. And what usually ends up happening is that you find ways to provide some nice and formal algorithmic optimizations in just a couple of places which make our program meet its performance requirements instead of having to go all over your entire code base and tweak and hack things in order to squeeze clock cycles here and there.

    0 讨论(0)
  • 2020-11-22 02:36

    One solution to this problem could be to provide a variable scope encapsulating the while loop:

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

    They would be automatically de-reference when the outer scope ends.

    0 讨论(0)
  • 2020-11-22 02:39

    Please skip to the updated answer...

    For those who care about performance take out the System.out and limit the loop to 1 byte. Using double (test 1/2) and using String (3/4) the elapsed times in milliseconds is given below with Windows 7 Professional 64 bit and JDK-1.7.0_21. Bytecodes (also given below for test1 and test2) are not the same. I was too lazy to test with mutable & relatively complex objects.

    double

    Test1 took: 2710 msecs

    Test2 took: 2790 msecs

    String (just replace double with string in the tests)

    Test3 took: 1200 msecs

    Test4 took: 3000 msecs

    Compiling and getting 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
    }
    

    UPDATED ANSWER

    It's really not easy to compare performance with all the JVM optimizations. However, it is somewhat possible. Better test and detailed results in Google Caliper

    1. Some details on blog:Should you declare a variable inside a loop or before the loop?
    2. GitHub repository: https://github.com/gunduru/jvdt
    3. Test Results for double case and 100M loop (and yes all JVM details): https://microbenchmarks.appspot.com/runs/b1cef8d1-0e2c-4120-be61-a99faff625b4

    DeclaredBefore 1,759.209 DeclaredInside 2,242.308

    • DeclaredBefore 1,759.209 ns
    • DeclaredInside 2,242.308 ns

    Partial Test Code for double Declaration

    This is not identical to the code above. If you just code a dummy loop JVM skips it, so at least you need to assign and return something. This is also recommended in Caliper documentation.

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

    Summary: declaredBefore indicates better performance -really tiny- and it's against the smallest scope principle. JVM should actually do this for you

    0 讨论(0)
  • 2020-11-22 02:39

    Declaring inside the loop limits the scope of the respective variable. It all depends on the requirement of the project on the scope of the variable.

    0 讨论(0)
  • 2020-11-22 02:39

    These two examples result in the same thing. However, the first provides you with using the str variable outside of the while loop; the second is not.

    0 讨论(0)
  • 2020-11-22 02:40

    I compared the byte code of those two (similar) examples:

    Let's look at 1. example:

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

    after javac Test.java, javap -c Test you'll get:

    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
    
    }
    

    Let's look at 2. example:

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

    after javac Test.java, javap -c Test you'll get:

    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
    
    }
    

    The observations shows that there is no difference among those two examples. It's the result of JVM specifications...

    But in the name of best coding practice it is recommended to declare the variable in the smallest possible scope (in this example it is inside the loop, as this is the only place where the variable is used).

    0 讨论(0)
提交回复
热议问题