被普遍误解的 Java 性能优化建议:循环内定义变量
在 Java 性能优化的各种资料和理念中,我们常看到一种观点:不应在循环内部定义变量,否则会占用过多内存、影响性能,而应将变量定义在循环外部。接触
Java 多年,相信不少开发者都曾被这一说法所误导。
请看以下两个代码示例,示例1将变量定义在循环外,示例2将变量定义在循环内。
java
/**
- 变量定义在循环外部
*/
private static void outerDefinition() {
ExampleObject obj = null;
for (int i = 0; i < 10; i++) {
obj = new ExampleObject();
}
}
/**
- 变量定义在循环内部
*/
private static void innerDefinition() {
for (int i = 0; i < 10; i++) {
ExampleObject obj = new ExampleObject();
}
}
我们来逐一分析这两种写法。
变量定义在循环外部
在这种写法中,变量在循环外声明,在循环内不断指向新创建的对象实例。每次循环变更引用时,上一个被引用的对象就会失去引用并等待垃圾回收,直到循环结束。循环结束后,这个变量依然存在,并指向最后一次循环创建的对象,而其他对象则已被回收。
如此一来,本该局限于循环体内的变量生命周期被延长到了循环体外。如果在循环结束后继续使用该变量,可能导致后续业务逻辑出现不可预料的后果。在实际工作中,此类问题并不少见,请看下面的例子:
java
private static void problematicOuter() {
ExampleObject obj1 = null;
for (int i = 0; i < 10; i++) {
obj1 = new ExampleObject();
}
ExampleObject obj2 = userDao.getUser(10);
// 若后续代码误写成 obj1,将引发错误
}
上面定义了一个 obj2,如果在后续代码或传递到其他方法时笔误写成了 obj1,就会产生问题。此外,如果使用同一个变量名,当该变量被重用并发生异常时,本应得到
null 的异常值,却可能获得之前循环中残留的值,导致逻辑混乱。
变量定义在循环内部
在循环内部定义变量时,每次迭代都会创建一个新的局部变量,并指向新实例。每个变量和对象的生命周期都被严格限制在当次循环体内,每次循环结束,对应的局部变量和对象实例都会随着栈帧的弹出而失效,进而被回收。因此,并不存在“占用更多内存”的说法。
结论
两种写法都会创建相同数量的对象实例。区别在于,循环内部定义会反复创建相同数量的局部变量,导致栈内存的垃圾回收频率略高。然而,与因变量生命周期延长可能引发的业务风险以及堆内存回收的潜在影响相比,栈内存这点微小的性能差异几乎可以忽略不计。
因此,更推荐在循环内部定义变量。这种做法将变量的作用域严格限制在循环体内,既能避免因变量意外重用导致的业务逻辑错误,也符合最小作用域原则,使代码更清晰、更安全。
