Java - 在for循环中声明变量

17

在循环内部声明变量是不良实践吗?我认为像下面第一个代码块中所示这样做会使用十倍的内存,因为每次循环迭代都会创建一个新字符串。这是正确的吗?

for (int i = 0; i < 10; i++) {
  String str = "Some string";
}

对比。

String str;
for (int i = 0; i < 10; i++) {
  str = "Some String";
}

2
字符串是不可变类,每次写入都会创建新的实例。但在您的情况下,由于常量值,编译器可能会进行优化。 - qrtt1
也许在这个例子中使用字符串并不是一个好的选择。我的真正问题是,如果一个变量在循环内部被声明,那么每次迭代都会声明一个新的变量,还是编译器会将其优化为单个变量? - dfetter88
3
变量声明和对象构造是两个独立的过程。如果您在执行N次的循环内部执行final Foo foo = new Foo(someArg);,它将构造N个不同的对象;而在循环外部执行则只会构造一次。但是,如果您在循环外部执行final Foo foo1 = new Foo(someArg);,然后在循环内部执行final Foo foo = foo1,那么只会实例化一个对象。字符串有点特殊,因为它们是不可变常量,编译器可能会将其优化为创建一个String对象,并在循环中重复使用它。 - Jason S
5个回答

26
在循环中声明变量是不良实践吗? 不是的!这将使变量局限于其使用点。 似乎在第一个代码块中这样做会使用十倍于第二个代码块的内存。 编译器可以通过优化来保持内存的有效使用。提示:如果使用“final”关键字告诉它变量对对象具有固定的引用,可以帮助它。 注意:如果您需要在构造函数中执行复杂代码的更复杂对象,则可能需要关注单个执行和多个执行,并在循环外声明该对象。

1
只是一个跟进..如果我们在循环中有String str = new String("Some String");会怎样?我的理解是它会创建10个实例! - rkg
1
@Ravi:你可能是对的,因为你明确地调用了String构造函数,我相当确定这会绕过字符串池化操作,从而无法重用常量字符串实例。 - Jason S
2
但即使在这种情况下,如果您将str的声明移动到循环外,并将赋值留在循环内,仍然会创建相同的10个实例。 - ILMTitan
@ILMTitan:是的,你是对的。我只是想知道即使有一个新操作符,JVM是否会进行优化。 - rkg
1
它将创建十个实例,是的,但您必须这样做九千多次才能产生任何显着的性能影响。保持变量在作用域内允许运行时快速释放该变量(并在迭代中重新使用它)-将变量移动到周围范围将使其在内存中保留时间更长。而且无论如何,您仍然会为每个迭代重新创建对象实例。 - cthulhu
了解本地变量的作用域是编译时的产物可能有所帮助。这两个循环的字节码是相同的。因此,它们的性能并不是由于任何优化的结果而相同,一开始就没有任何区别。 - Holger

7
在这两个例子中,你将实例化一个包含相同次数的字符串“Some String”的新字符串对象。
在第一个例子中,您在循环内声明str,所有对该字符串的引用都将在for循环完成后丢失,从而允许Java的垃圾收集器从内存中删除所有字符串实例。但是,在第二个例子中,您在循环外部声明str,您创建的最后一个字符串仍将在循环外部具有对它的引用,Java的垃圾收集器只会从内存中删除10个字符串中的9个实例。
因此,第一种方法更好,因为您不会保留任何字符串的引用,从而干扰垃圾收集器确定它是否仍在使用的能力。

2
除了@Jason S所说的,我还鼓励您考虑代码的可读性。
例如,如果您只写一次到引用,使用以下代码将使您的意图更加清晰:(译者注:此处为代码示例)
String str = "write once";
while(condition){
    //do stuff with str
}

对比:

String str = null;
while(condition){
    str = "write once";
    //do stuff with str
}

同样的,如果字符串的值是基于循环迭代中特定的某些内容,那么应该在循环内部声明变量。

1
变量引用使用的内存很小。通常最好的做法是在循环内部声明该项,因为它会更接近使用它的位置并且更易读。

变量引用使用的内存确实很小。但是,如果循环从0到10000000000迭代,并且使用代码块#2仅使用一个变量引用(而不是对于块#1的每次迭代都使用一个),那么您肯定可以看到这将是一个问题。 - dfetter88
1
是的,即使你循环了这么多次,也不应该有太大的影响。正如其他人所说,编译器甚至可能会将其优化掉,您可以进行测试,以确保正确性。但无论如何,为了获得微不足道的性能提升而使代码变得难以阅读的代价是不值得的。 - jzd

0

这要看情况。如果您的编译器没有做任何更改,那么低效率是由于创建String对象时产生的开销。一旦超出作用域,内存将被清除。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接