關於Java是否應該在循環外聲明變量的一點思考

前言

“不要把變量聲明在循環體內”,經常看到類似的言論,那麼到底有沒有必要這麼去做呢?

首先,將變量聲明在循環體外有以下幾個缺點:

  • 作用域變大,存在被無意引用的風險
  • 防止變量命名衝突
  • 可讀性較差

綜上,如果“在循環體外聲明變量”不能在其他方面(如性能上)帶來優化,那麼我實在想不出有什麼理由需要這麼去做。

性能和內存

在語法的可讀性上,“循環外聲明變量”是不佔優勢的,如果說它可能存在的其他優勢,我能想到的就只有在性能上的提升、和內存上的優化了。

性能測試

如下測試代碼,循環內構建一千萬個實例,筆者經過測試均在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回收。
所以,如果確實只需要在循環內使用變量,那就放心大膽的在循環內聲明吧,沒有任何問題!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章