前言
“不要把變量聲明在循環體內”,經常看到類似的言論,那麼到底有沒有必要這麼去做呢?
首先,將變量聲明在循環體外有以下幾個缺點:
- 作用域變大,存在被無意引用的風險
- 防止變量命名衝突
- 可讀性較差
綜上,如果“在循環體外聲明變量”不能在其他方面(如性能上)帶來優化,那麼我實在想不出有什麼理由需要這麼去做。
性能和內存
在語法的可讀性上,“循環外聲明變量”是不佔優勢的,如果說它可能存在的其他優勢,我能想到的就只有在性能上的提升、和內存上的優化了。
性能測試
如下測試代碼,循環內構建一千萬個實例,筆者經過測試均在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回收。
所以,如果確實只需要在循環內使用變量,那就放心大膽的在循環內聲明吧,沒有任何問題!