在循环内部或外部声明variables

为什么以下工作正常?

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

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

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

是否有必要在循环之外声明variables?

局部variables的范围始终应尽可能小。

在你的例子中,我认为str 不在 while循环之外,否则你不会问这个问题,因为在while循环中声明它不会是一个选项,因为它不会被编译。

所以,因为str 不在循环外部使用, str的最小可能范围 while循环内。

所以,答案是强调 ,绝对应该在while循环内声明。 没有,如果没有,但不是。

这个规则可能被违反的唯一情况是,如果由于某种原因,每个时钟周期必须被挤出代码是非常重要的,在这种情况下,你可能要考虑在外部范围内实例化一些东西,而不是重用它在内部作用域的每次迭代中重新实例化它。 然而,这不适用于你的例子,由于在java中string的不变性:一个新的str实例总是会在你的循环开始时被创build,并且它将不得不在它的末尾被抛弃,所以在那里在那里优化是不可能的。

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

在任何情况下,正确的做法是正确编写所有代码,为您的产品build立一个性能要求,根据这个要求来衡量您的最终产品,如果不能满足要求,那就优化一下。 通常最终发生的事情是,你可以find方法来在几个地方提供一些很好的和forms化的algorithm优化,这使得我们的程序满足了它的性能要求,而不必遍历整个代码库,调整和破解为了挤压时钟周期在这里和那里。

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

我们来看一个例子

 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.javajavap -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.javajavap -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规范的结果…

但是,以最佳编码实践的名义,build议将variables声明在尽可能小的范围内(在这个例子中是在循环内部,因为这是唯一使用variables的地方)。

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

性能对今天的编译器无关紧要(在这种情况下)
从维护的angular度来看, 第二种select更好。
在尽可能最窄的范围内声明和初始化variables。

正如唐纳德·埃文·克努特所说:

“我们应该忘记小效率,大约97%的时间:不成熟的优化是万恶的根源”

即)程序员让性能考虑影响一段代码的devise的情况。 这可能会导致devise不够清晰或者代码不正确,因为代码由于优化变得复杂 ,程序员因为优化而分心。

如果你想使用外部循环也; 在外面宣布。 否则,第二版是好的。

在里面,variables的范围越小越好。

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

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

是更好的,因为如果你只在condition为真时在栈上定义一个对象。 即使用它, 如果你需要它

请跳到更新的答案…

对于那些关心性能的人来说,取出System.out并将循环限制为1个字节。 使用double(test 1/2)并使用String(3/4),经过的时间(以毫秒为单位)在Windows 7 Professional 64位和JDK-1.7.0_21中给出。 字节码(下面给出的test1和test2)也不相同。 我懒得testing可变和相对复杂的对象。

Test1采取:2710毫秒

Test2采取:2790 msecs

string(只需在testing中用stringreplacedouble)

Test3采取:1200毫秒

Test4采取:3000毫秒

编译并获取字节码

 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 } 

更新的答案

将性能与所有JVM优化进行比较并不容易。 但是,这是有点可能的。 在Google Caliper中进行更好的testing和详细的结果

  1. 博客上的一些细节: 你应该在一个循环内还是在循环之前声明一个variables?
  2. GitHub存储库: https : //github.com/gunduru/jvdt
  3. 双案例和100M循环的testing结果(以及是所有JVM详细信息): https : //microbenchmarks.appspot.com/runs/b1cef8d1-0e2c-4120-be61-a99faff625b4

宣布前1,759.209宣布内部2,242.308

  • 在1,759.209纳秒前宣布
  • DeclaredInside 2,242.308 ns

双重声明的部分testing代码

这与上面的代码不一样。 如果你只编写一个虚拟循环JVM跳过它,所以至less你需要分配和返回的东西。 这在Caliper文档中也是推荐的。

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

我认为回答你的问题最好的资源将是以下post:

在循环之前或循环中声明variables之间的区别?

根据我的理解,这件事情会依赖于语言。 IIRC Java对此进行了优化,因此没有任何区别,但是JavaScript(例如)会在循环中每次执行整个内存分配。特别是在Java中,我认为在完成性能分析时,第二个将运行得更快。

在wile循环之外声明String str允许在while循环内部和外部引用它。 在while循环中声明String str, 允许在while循环中被引用。

解决这个问题的一个办法是提供一个封装while循环的variables作用域:

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

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

variables应该被声明为尽可能靠近它们的位置。

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

它保持variables的范围很紧。 这可以让优化器更好地工作。

根据谷歌Android开发指南,variables的范围应该是有限的。 请检查这个链接:

限制variables范围

上述问题确实是一个编程问题。 你想如何编程你的代码? 您需要在哪里访问“STR”? 没有使用声明在本地用作全局variables的variables。 我相信编程的基础。

即使在执行代码之后, strvariables也是可用的,并保留了一些空间。

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

strvariables将不可用,并且内存将在下面的代码中为strvariables分配。

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

如果我们跟着第二个肯定会减less我们的系统内存并提高性能。

在循环内声明限制了相应variables的范围。 这一切都取决于项目对variables范围的要求。

正如很多人所指出的那样,

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

并不比这更好:

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

所以如果你不重复使用它,就不要在它们的作用域之外声明variables。

这两个例子导致相同的事情。 不过,第一个提供的是在while循环之外使用strvariables; 第二个不是。

我认为对象的大小也很重要。 在我的一个项目中,我们已经声明并初始化了一个大的二维数组,这使得应用程序抛出了内存不足的exception。 我们将声明移出循环,并在每次迭代开始时清除数组。

如果您的calculateStr()方法返回null ,然后尝试调用str上的方法,那么您有NullPointerException的风险。

更一般地说,避免使用值的variables。 顺便说一下,这对于class级属性来说更强。

几乎所有人都在这个问题上的警告:这里是示例代码,在循环内部,使用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); } } 

结论:根据局部variables的大小,即使没有那么大的variables,差异也是巨大的。

只是有时候,在循环之外或之内是很重要的。