如果您真的想查看字节码的差异,请使用javap -c。用 Eclipse 编译器编译,第一个版本(在源代码中有一个局部变量)给出了:
public static void test(java.lang.String);
Code:
0: aload_0
1: ldc #16; //String ;
3: invokevirtual #18; //Method java/lang/String.split:(Ljava/lang/String;)[Ljava/lang/String;
6: astore_1
7: aload_1
8: dup
9: astore 5
11: arraylength
12: istore 4
14: iconst_0
15: istore_3
16: goto 27
19: aload 5
21: iload_3
22: aaload
23: astore_2
24: iinc 3, 1
27: iload_3
28: iload 4
30: if_icmplt 19
33: return
}
第二个:
public static void test(java.lang.String);
Code:
0: aload_0
1: ldc #16; //String ;
3: invokevirtual #18; //Method java/lang/String.split:(Ljava/lang/String;)[Ljava/lang/String;
6: dup
7: astore 4
9: arraylength
10: istore_3
11: iconst_0
12: istore_2
13: goto 24
16: aload 4
18: iload_2
19: aaload
20: astore_1
21: iinc 2, 1
24: iload_2
25: iload_3
26: if_icmplt 16
29: return
}
如你所见(如果你能读懂的话),第一个变体确实使用了一个额外的局部变量槽(astore_1/aload_1 开头)。但是(a)即使字节码被解释,它的开销也可以忽略不计(它只是复制引用)并且(b)任何 JIT 编译器都能够优化它,无论您是否事先进行了一些静态字节码优化: 局部变量 (1) 以后永远不会使用。
源代码中的局部变量主要用于清晰,以及能够重用中间结果。从根本上说,第二个变体(没有显式的局部变量)不会让您在方法中进一步使用split 的结果(甚至直接在循环中),而在代码中命名局部变量时可以.
有了第二段代码,我想知道编译器是否足够聪明
只做一次拆分
这两种方法的唯一区别是在源代码中显式声明了一个局部变量。
在这两种情况下,for (String f: flags.split(";")) { } 是语法糖(除了两个局部变量都不可访问)等价于:
int _hidden_i = 0;
for (String[] _hidden_arr=flags.split(";"); _hidden_i<_hidden_arr.length; _hidden_i++){
String f = _hidden_arr[_hidden_i];
// some stuff
}
需要注意的是:
- 使用这种表示法,
_hidden_i 永远不会暴露,因此您永远无法将某些内容分配给 _hidden_arr[_hidden_i]。
- 即使是一个天真的源代码到字节码编译器也知道
_hidden_arr 和_hidden_i 将永远不会再次使用,因此它不会复制它们以供进一步使用。
根本不同的是:
for (var i = 0; i < flags.split(";").length; i++) {
String f = flags.split(";")[i];
}
在for(;;) 表示法中,第一部分总是执行一次。第二个表达式总是在进入循环块之前执行(以测试是否运行它)。最后一个总是在运行循环块之后执行(除非使用break)。
您通常希望避免在第二部分中可能很繁重的方法调用,尤其是当它总是会得到相同的结果时。