关于Java是否应该在循环外声明变量的一点思考

70 阅读2分钟

目录

前言

“不要把变量声明在循环体内”,经常看到类似的言论,那么到底有没有必要这么去做呢?

首先,将变量声明在循环体外有以下几个缺点:

  • 作用域变大,存在被无意引用的风险
  • 防止变量命名冲突
  • 可读性较差

综上,如果“在循环体外声明变量”不能在其他方面(如性能上)带来优化,那么我实在想不出有什么理由需要这么去做。

性能和内存

在语法的可读性上,“循环外声明变量”是不占优势的,如果说它可能存在的其他优势,我能想到的就只有在性能上的提升、和内存上的优化了。

性能测试

如下测试代码,循环内构建一千万个实例,笔者经过测试均在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回收。
所以,如果确实只需要在循环内使用变量,那就放心大胆的在循环内声明吧,没有任何问题!