为什么下面的工作正常?

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

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

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

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


当前回答

在最小范围内声明对象可以提高可读性。

性能对于今天的编译器来说并不重要。(在此场景中) 从维护的角度来看,第二种选择更好。 在同一个地方声明和初始化变量,在尽可能窄的范围内。

正如Donald Ervin Knuth所说:

“我们应该忘记小的效率,大约97%的时候: 过早的优化是万恶之源”

例如,程序员让性能考虑影响一段代码的设计的情况。这可能导致设计不那么清晰,或者代码不正确,因为优化使代码变得复杂,而程序员被优化分散了注意力。

其他回答

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

在你的例子中,我假设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.

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

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

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

当外部作用域结束时,它们将自动取消引用。

正如很多人指出的那样,

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

没有比这更好的了:

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

因此,如果你不重用变量,就不要在变量作用域之外声明变量。

在while循环之外声明String str允许它在while循环内外被引用。在while循环中声明String str只允许它在while循环中被引用。