我一直想知道,在一般情况下,在循环之前声明一个丢弃的变量,而不是在循环内部重复,使任何(性能)差异? Java中的一个(非常无意义的)例子:

A)循环前声明:

double intermediateResult;
for(int i=0; i < 1000; i++){
    intermediateResult = i;
    System.out.println(intermediateResult);
}

B)声明(重复)内部循环:

for(int i=0; i < 1000; i++){
    double intermediateResult = i;
    System.out.println(intermediateResult);
}

哪个更好,a还是b?

我怀疑重复的变量声明(例b)在理论上会产生更多的开销,但编译器足够聪明,所以这无关紧要。例b的优点是更紧凑,并且将变量的作用域限制在使用它的地方。尽管如此,我还是倾向于根据示例a编写代码。

编辑:我对Java案例特别感兴趣。


当前回答

当我想在退出循环后查看变量的内容时,我使用(A)。它只与调试有关。当我希望代码更紧凑时,我使用(B),因为它节省了一行代码。

其他回答

我总是使用A(而不是依赖于编译器),也可能重写为:

for(int i=0, double intermediateResult=0; i<1000; i++){
    intermediateResult = i;
    System.out.println(intermediateResult);
}

这仍然将intermediateResult限制在循环的范围内,但不会在每次迭代期间重新声明。

很长一段时间我都有同样的问题。所以我测试了一段更简单的代码。

结论:对于此类病例,两组间无显著性差异。

外环箱

int intermediateResult;
for(int i=0; i < 1000; i++){
    intermediateResult = i+2;
    System.out.println(intermediateResult);
}

内环箱

for(int i=0; i < 1000; i++){
    int intermediateResult = i+2;
    System.out.println(intermediateResult);
}

我检查了IntelliJ的反编译器上的编译文件,对于这两种情况,我得到了相同的Test.class

for(int i = 0; i < 1000; ++i) {
    int intermediateResult = i + 2;
    System.out.println(intermediateResult);
}

我还使用这个答案中给出的方法对这两种情况的代码进行了反汇编。我将只展示与答案相关的部分

外环箱

Code:
  stack=2, locals=3, args_size=1
     0: iconst_0
     1: istore_2
     2: iload_2
     3: sipush        1000
     6: if_icmpge     26
     9: iload_2
    10: iconst_2
    11: iadd
    12: istore_1
    13: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
    16: iload_1
    17: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
    20: iinc          2, 1
    23: goto          2
    26: return
LocalVariableTable:
        Start  Length  Slot  Name   Signature
           13      13     1 intermediateResult   I
            2      24     2     i   I
            0      27     0  args   [Ljava/lang/String;

内环箱

Code:
      stack=2, locals=3, args_size=1
         0: iconst_0
         1: istore_1
         2: iload_1
         3: sipush        1000
         6: if_icmpge     26
         9: iload_1
        10: iconst_2
        11: iadd
        12: istore_2
        13: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
        16: iload_2
        17: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
        20: iinc          1, 1
        23: goto          2
        26: return
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
           13       7     2 intermediateResult   I
            2      24     1     i   I
            0      27     0  args   [Ljava/lang/String;

如果您仔细注意,在LocalVariableTable中,只有分配给i和intermediateResult的Slot会根据它们的出现顺序进行交换。slot中的相同差异也反映在其他代码行中。

当前无额外操作 在这两种情况下,intermediateResult仍然是一个局部变量,因此访问时间没有差异。

奖金

编译器做了大量的优化,看看在这种情况下发生了什么。

零工作情况

for(int i=0; i < 1000; i++){
    int intermediateResult = i;
    System.out.println(intermediateResult);
}

零工作反编译

for(int i = 0; i < 1000; ++i) {
    System.out.println(i);
}

作为一般规则,我将变量声明在最内部的可能范围内。如果你不在循环外使用intermediateResult,那么我会用B。

我认为b是更好的结构。在a中,intermediateResult的最后一个值在循环结束后保留。

编辑: 这与值类型没有太大区别,但引用类型可能会有些重要。就我个人而言,我喜欢尽快解除对变量的引用以进行清理,b为你做了这件事,

哪个更好,a还是b?

从性能的角度来看,你必须衡量它。(在我看来,如果你能测量到差异,编译器不是很好)。

从维护的角度来看,b更好。在同一个地方声明和初始化变量,在尽可能窄的范围内。不要在声明和初始化之间留下空隙,不要污染不需要的名称空间。