为什么下面的工作正常?
String str;
while (condition) {
str = calculateStr();
.....
}
但是下面这个被认为是危险的/不正确的:
while (condition) {
String str = calculateStr();
.....
}
有必要在循环之外声明变量吗?
为什么下面的工作正常?
String str;
while (condition) {
str = calculateStr();
.....
}
但是下面这个被认为是危险的/不正确的:
while (condition) {
String str = calculateStr();
.....
}
有必要在循环之外声明变量吗?
当前回答
如果你不需要在while循环之后使用str(范围相关),那么第二个条件,即。
while(condition){
String str = calculateStr();
.....
}
如果你只在条件为真时才在堆栈上定义一个对象,那就更好了。也就是说,如果你需要,就使用它
其他回答
str变量将可用,并在内存中保留一些空间,即使在执行下面的代码之后。
String str;
while(condition){
str = calculateStr();
.....
}
str变量将不可用,内存也将被释放,这是在下面的代码中为str变量分配的。
while(condition){
String str = calculateStr();
.....
}
如果我们采用第二种方法,肯定会减少系统内存,提高性能。
在内部,变量可见的范围越小越好。
我认为回答你的问题最好的资源是下面的帖子:
在循环前或循环中声明变量的区别?
根据我的理解,这个东西是语言依赖的。IIRC Java优化了这一点,所以没有任何区别,但JavaScript(例如)将在每次循环中完成整个内存分配。特别是在Java中,我认为当完成分析时,第二个程序会运行得更快。
请跳到最新的答案…
对于那些关心绩效的人,可以去掉系统。输出并将循环限制为1字节。使用double (test 1/2)和使用String(3/4),在Windows 7 Professional 64位和JDK-1.7.0_21上运行的时间以毫秒为单位,如下所示。字节码(下面也给出了test1和test2的字节码)是不同的。我懒得测试可变和相对复杂的对象。
双
Test1耗时:2710毫秒
Test2耗时:2790毫秒
字符串(在测试中用字符串替换double)
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优化的性能确实不容易。然而,这在某种程度上是可能的。在谷歌卡尺中更好的测试和详细的结果
关于博客的一些细节:应该在循环内部还是循环之前声明变量? GitHub存储库:https://github.com/gunduru/jvdt 双case和100M循环的测试结果(是的所有JVM细节):https://microbenchmarks.appspot.com/runs/b1cef8d1-0e2c-4120-be61-a99faff625b4
声明前1759.209 ns DeclaredInside 2,242.308 ns
双重声明的部分测试代码
这与上面的代码并不相同。如果您只是编写了一个虚拟循环,JVM将跳过它,因此至少您需要赋值并返回一些东西。在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;
}
总结:declardbefore表示更好的性能——非常小——它违背了最小作用域原则。JVM实际上应该为您做这件事
如果你的calculateStr()方法返回null,然后你试图在str上调用一个方法,你有NullPointerException的风险。
更一般地说,避免使用空值变量。顺便说一下,它对类属性更强。