目录
前言
“不要把变量声明在循环体内”,经常看到类似的言论,那么到底有没有必要这么去做呢?
首先,将变量声明在循环体外有以下几个缺点:
- 作用域变大,存在被无意引用的风险
- 防止变量命名冲突
- 可读性较差
综上,如果“在循环体外声明变量”不能在其他方面(如性能上)带来优化,那么我实在想不出有什么理由需要这么去做。
性能和内存
在语法的可读性上,“循环外声明变量”是不占优势的,如果说它可能存在的其他优势,我能想到的就只有在性能上的提升、和内存上的优化了。
性能测试
如下测试代码,循环内构建一千万个实例,笔者经过测试均在5ms完成,性能几乎没有差别。
public class VariableLoopTest {
public static void main(String[] args) {
//inside();
//outside();
}
static void inside() {
long t1 = System.currentTimeMillis();
for (int i = 0; i < 10000000; i++) {
Object o = new Object();
}
long t2 = System.currentTimeMillis();
System.out.println(t2 - t1);
}
static void outside(){
long t1 = System.currentTimeMillis();
Object o = null;
for (int i = 0; i < 10000000; i++) {
o = new Object();
}
long t2 = System.currentTimeMillis();
System.out.println(t2 - t1);
}
}
内存测试
如下测试代码,构建3个10MB大小的实例,手动触发GC,查看内存的回收情况。
public class MemoryTest {
private static final int _1MB = 1024 * 1024;
//单个实例占用10MB内存
static class MyClass {
byte[] bytes = new byte[_1MB * 10];
}
/**
* VM Args: -XX:+PrintGCDetails 输出GC日志
*/
public static void main(String[] args) throws InterruptedException {
//inside();
outside();
}
static void inside(){
for (int i = 0; i < 3; i++) {
MyClass myClass = new MyClass();
}
System.gc();
}
static void outside(){
MyClass myClass = null;
for (int i = 0; i < 3; i++) {
myClass = new MyClass();
}
System.gc();
}
}
如上测试结果,在循环内声明的变量,只要循环体运行结束,对象可以很好的被回收。
在循环外声明变量,最后一个对象仍然被引用,反而会影响GC回收。
由此可见,“循环外声明变量”在性能和内存上都不占优势。
编译优化
事实上,开发者写的代码经过javac编译之后,编译器会对代码做一些优化和调整。
只要变量没有被循环体外的代码访问,那么编译器会自动将变量的声明放到循环提内。
如下测试代码:
public class VariableLoop {
void inside(){
for (int i = 0; i < 1000; i++) {
Object o = new Object();
}
}
void outside(){
Object o;
for (int i = 0; i < 1000; i++) {
o = new Object();
}
}
}
使用javac编译后:
public class VariableLoop {
public VariableLoop() {
}
void inside() {
for(int i = 0; i < 1000; ++i) {
new Object();
}
}
void outside() {
for(int i = 0; i < 1000; ++i) {
new Object();
}
}
}
使用javap -c对字节码进行反汇编,生成的指令也无差别,如下:
void inside();
Code:
0: iconst_0
1: istore_1
2: iload_1
3: sipush 1000
6: if_icmpge 23
9: new #2 // class java/lang/Object
12: dup
13: invokespecial #1 // Method java/lang/Object."<init>":()V
16: astore_2
17: iinc 1, 1
20: goto 2
23: return
void outside();
Code:
0: iconst_0
1: istore_2
2: iload_2
3: sipush 1000
6: if_icmpge 23
9: new #2 // class java/lang/Object
12: dup
13: invokespecial #1 // Method java/lang/Object."<init>":()V
16: astore_1
17: iinc 2, 1
20: goto 2
23: return
总结
综上所述,“循环外声明变量”除了扩大变量的作用域、可读性差之外,在性能和内存上也没有什么提升,反而还会影响对象的GC回收。
所以,如果确实只需要在循环内使用变量,那就放心大胆的在循环内声明吧,没有任何问题!