对象创建位置差异的详细分析
对象创建位置差异的详细分析
代码对比
代码一:循环外创建对象引用
public List<Account> selectAll(){Connection conn=null;PreparedStatement ps=null;ResultSet rs=null;Account act=null; // 🟡 在循环外声明对象引用List<Account> actList=new ArrayList<>();try {conn = DBUtil.getConnection();String sql="select id,actno,balance from act";ps=conn.prepareStatement(sql);rs = ps.executeQuery();while(rs.next()){Integer id = rs.getInt("id");String actno = rs.getString("actno");Double balance = rs.getDouble("balance");act=new Account(); // 🟡 在循环内创建新对象act.setId(id);act.setActno(actno);act.setBalance(balance);actList.add(act);}} catch (SQLException e) {throw new RuntimeException(e);}return actList;
}
代码二:循环内创建对象
public List<Account> selectAll(){Connection conn=null;PreparedStatement ps=null;ResultSet rs=null;List<Account> actList=new ArrayList<>();try {conn = DBUtil.getConnection();String sql="select id,actno,balance from act";ps=conn.prepareStatement(sql);rs = ps.executeQuery();while(rs.next()){Integer id = rs.getInt("id");String actno = rs.getString("actno");Double balance = rs.getDouble("balance");Account act=new Account(); // 🟢 在循环内声明和创建对象act.setId(id);act.setActno(actno);act.setBalance(balance);actList.add(act);}} catch (SQLException e) {throw new RuntimeException(e);}return actList;
}
核心区别分析
1. 作用域差异
特性 | 代码一 | 代码二 |
---|---|---|
作用域 | 方法级作用域 | 循环块级作用域 |
生命周期 | 整个方法执行期间 | 单次循环迭代期间 |
可访问性 | 循环内外都可访问 | 仅在循环内部可访问 |
2. 内存使用分析
代码一的内存变化:
Account act=null; // 栈内存:1个引用变量(初始为null)
while(rs.next()){act=new Account(); // 堆内存:创建新对象,引用指向新对象// 设置属性...actList.add(act); // 集合持有对象引用
}
// 循环结束后,act变量指向最后一个创建的Account对象
代码二的内存变化:
while(rs.next()){Account act=new Account(); // 每次循环:栈上新引用 + 堆上新对象// 设置属性...actList.add(act); // 集合持有对象引用
}
// 循环结束后,act变量不可访问
详细技术差异
1. 变量生命周期
// 代码一:变量在整个方法周期存在
public void example1() {Account act = null; // 作用域开始for (int i = 0; i < 3; i++) {act = new Account(); // 重复使用同一个引用变量actList.add(act);}System.out.println(act); // 这里还能访问act,指向最后一个对象
} // 作用域结束// 代码二:变量在每次循环中重新创建
public void example2() {for (int i = 0; i < 3; i++) {Account act = new Account(); // 每次循环创建新的引用变量actList.add(act);}// System.out.println(act); // 编译错误:act不可访问
}
2. 垃圾回收影响
实际上两种方式在垃圾回收方面没有区别,因为:
- 所有创建的
Account
对象都被actList
引用 - 只要
actList
存在,所有对象都不会被GC回收 - 方法结束后,局部变量都会被清理
潜在问题分析
代码一的潜在风险
// 错误示例:错误的重用对象引用
public List<Account> selectAll_ERROR(){Account act = new Account(); // 🚫 错误:在循环外创建对象List<Account> actList = new ArrayList<>();while(rs.next()){// 只是设置属性,没有创建新对象!act.setId(rs.getInt("id"));act.setActno(rs.getString("actno"));act.setBalance(rs.getDouble("balance"));actList.add(act); // 🚫 严重错误:添加的是同一个对象引用!}return actList; // 列表中所有元素都是同一个对象!
}
结果:列表中所有元素指向同一个Account
对象,所有账户信息相同(都是最后一条记录的值)。
正确的对象创建模式
// 正确:每次循环都创建新对象
while(rs.next()){Account act = new Account(); // ✅ 确保每次都是新对象act.setId(rs.getInt("id"));act.setActno(rs.getString("actno"));act.setBalance(rs.getDouble("balance"));actList.add(act);
}
性能对比
内存使用
- 栈内存:代码一比代码二多一个长期存在的引用变量(可忽略不计)
- 堆内存:两者完全相同,都创建了N个Account对象
- GC压力:无差异
执行效率
- 对象创建:两者都是每次循环创建新对象,开销相同
- 变量分配:代码一的引用变量只分配一次,代码二每次循环都分配(但JVM会优化)
最佳实践建议
推荐使用代码二的原因:
-
作用域最小化原则
// 好的实践:变量在最小作用域内声明 while(rs.next()){Account act = new Account(); // 作用域仅限于当前循环// 使用act... }
-
避免意外的引用重用
// 代码一可能被误修改为危险模式 Account act = new Account(); // 有人可能把创建移到循环外 while(rs.next()){// 只是设置属性,忘记创建新对象act.setId(rs.getInt("id"));actList.add(act); // 错误! }
-
代码清晰性
// 代码二更清晰地表达"每次循环创建新对象" while(rs.next()){Account act = new Account(); // 明确显示对象创建// ... }
-
便于重构和优化
// 代码二更容易转换为Stream API return DBUtil.executeQuery(sql, rs -> {List<Account> list = new ArrayList<>();while(rs.next()){Account act = new Account();// 设置属性list.add(act);}return list; });
实际测试验证
测试代码
public class ObjectCreationTest {@Testpublic void testBothMethodsProduceSameResult() {// 模拟数据库结果集List<Account> result1 = method1(); // 代码一方式List<Account> result2 = method2(); // 代码二方式// 验证结果完全相同assertEquals(result1.size(), result2.size());for (int i = 0; i < result1.size(); i++) {assertEquals(result1.get(i).getId(), result2.get(i).getId());assertEquals(result1.get(i).getActno(), result2.get(i).getActno());assertEquals(result1.get(i).getBalance(), result2.get(i).getBalance());}}// 验证不是同一个对象引用@Testpublic void testDifferentObjectReferences() {List<Account> result = method2();for (int i = 0; i < result.size() - 1; i++) {assertNotSame(result.get(i), result.get(i + 1)); // 确保是不同的对象}}
}
总结
特性 | 代码一(循环外声明) | 代码二(循环内声明) |
---|---|---|
功能结果 | ✅ 完全相同 | ✅ 完全相同 |
内存使用 | ⚠️ 稍多栈空间 | 🟢 栈空间更优 |
代码安全 | ⚠️ 有误用风险 | 🟢 更安全 |
可读性 | ⚠️ 一般 | 🟢 更清晰 |
维护性 | ⚠️ 一般 | 🟢 更易维护 |
性能 | 🟢 无差异 | 🟢 无差异 |
最终建议:优先使用代码二的写法(在循环内创建对象),因为它更符合编码最佳实践,作用域更清晰,且能避免潜在的错误。虽然两种方式的功能结果完全相同,但代码二在可维护性和安全性方面更优。