为什么下面的工作正常?

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

但是下面这个被认为是危险的/不正确的:

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

有必要在循环之外声明变量吗?


当前回答

变量的声明应该尽可能靠近使用它们的地方。

它使RAII(资源获取初始化)更容易。

它使变量的范围保持紧凑。这让优化器工作得更好。

其他回答

我比较了这两个(相似的)例子的字节代码:

我们来看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

}

观察结果表明,这两个例子没有什么不同。这是JVM规范的结果……

但是为了最佳编码实践的名义,建议在尽可能小的范围内声明变量(在本例中,它是在循环内部,因为这是唯一使用变量的地方)。

如果你不需要在while循环之后使用str(范围相关),那么第二个条件,即。

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

如果你只在条件为真时才在堆栈上定义一个对象,那就更好了。也就是说,如果你需要,就使用它

如果你想在循环外使用STR;在外面宣布。否则,第二版就可以了。

在这个问题上对几乎所有人的警告:下面是示例代码,在我的Java 7计算机上,循环内部的速度很容易慢200倍(内存消耗也略有不同)。但这不仅关乎范围,还关乎分配。

public class Test
{
    private final static int STUFF_SIZE = 512;
    private final static long LOOP = 10000000l;

    private static class Foo
    {
        private long[] bigStuff = new long[STUFF_SIZE];

        public Foo(long value)
        {
            setValue(value);
        }

        public void setValue(long value)
        {
            // Putting value in a random place.
            bigStuff[(int) (value % STUFF_SIZE)] = value;
        }

        public long getValue()
        {
            // Retrieving whatever value.
            return bigStuff[STUFF_SIZE / 2];
        }
    }

    public static long test1()
    {
        long total = 0;

        for (long i = 0; i < LOOP; i++)
        {
            Foo foo = new Foo(i);
            total += foo.getValue();
        }

        return total;
    }

    public static long test2()
    {
        long total = 0;

        Foo foo = new Foo(0);
        for (long i = 0; i < LOOP; i++)
        {
            foo.setValue(i);
            total += foo.getValue();
        }

        return total;
    }

    public static void main(String[] args)
    {
        long start;

        start = System.currentTimeMillis();
        test1();
        System.out.println(System.currentTimeMillis() - start);

        start = System.currentTimeMillis();
        test2();
        System.out.println(System.currentTimeMillis() - start);
    }
}

结论:根据局部变量的大小,即使变量不是那么大,差异也可能是巨大的。

只是说,有时候,在循环外或循环内确实很重要。

局部变量的作用域应该总是尽可能的小。

在你的例子中,我假设str没有在while循环之外使用,否则你就不会问这个问题,因为在while循环内部声明它不是一个选项,因为它不会编译。

因此,由于str不在循环之外使用,因此str的最小作用域是在while循环内。

因此,答案强调str绝对应该在while循环中声明。没有如果,没有并且,没有但是。

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.

编辑:(在答案下面注入我的评论)

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.