当循环开始"内卷"
想象你要清点一个仓库:
🔢 for循环 → 拿着清单逐个核对(自己控制每个步骤)
🚀 foreach循环 → 智能机器人自动扫描(全自动遍历)
Java中的这两种循环就像不同的清点方式,今天我们就来揭秘它们的区别和最佳使用场景!
一、基本语法对比
1. 传统for循环
for (初始化; 条件; 步进) {
// 循环体
}
// 数组遍历示例
int[] nums = {1, 2, 3};
for (int i = 0; i < nums.length; i++) {
System.out.println(nums[i]);
}
2. 增强for循环(foreach)
for (元素类型 变量名 : 集合或数组) {
// 循环体
}
// 同数组的foreach遍历
for (int num : nums) {
System.out.println(num);
}
二、核心区别五维分析
维度 | for循环 | foreach循环 |
---|---|---|
控制权 | 完全掌控索引 | 自动迭代 |
可读性 | 较冗长 | 简洁直观 |
修改集合 | 可安全删除元素 | 遍历时修改会抛异常 |
性能 | 数组遍历略快 | 集合遍历更优 |
适用场景 | 需要索引/复杂控制 | 简单遍历 |
三、使用场景对决
1. for循环擅长场景
// 场景1:需要索引时
for (int i = 0; i < list.size(); i++) {
System.out.println(i + ": " + list.get(i));
}
// 场景2:逆序遍历
for (int i = array.length - 1; i >= 0; i--) {
System.out.println(array[i]);
}
// 场景3:间隔访问
for (int i = 0; i < 100; i += 5) {
System.out.println(i);
}
2. foreach循环擅长场景
// 场景1:简单集合遍历
List<String> names = Arrays.asList("Alice", "Bob");
for (String name : names) {
System.out.println(name);
}
// 场景2:多维数组
int[][] matrix = {{1,2}, {3,4}};
for (int[] row : matrix) {
for (int num : row) {
System.out.print(num + " ");
}
}
// 场景3:实现Iterable的自定义类
for (Product p : shoppingCart) { // 购物车实现Iterable
p.display();
}
四、性能对比实测
1. 数组遍历测试
数组遍历性能对比(百万次)
int[] array = new int[10_000_000];
// for循环
long start = System.nanoTime();
for (int i = 0; i < array.length; i++) {
array[i] = i;
}
long forTime = System.nanoTime() - start;
// foreach循环
start = System.nanoTime();
for (int num : array) {
num = num; // 实际无法修改数组元素!
}
long foreachTime = System.nanoTime() - start;
System.out.println("数组-for: " + forTime + "ns");
System.out.println("数组-foreach: " + foreachTime + "ns");
结果:for循环快约15%(因foreach隐藏了索引访问)
2. 集合遍历测试
List<Integer> list = new ArrayList<>();
// 填充10_000_000个元素...
// for循环
start = System.nanoTime();
for (int i = 0; i < list.size(); i++) {
list.get(i);
}
forTime = System.nanoTime() - start;
// foreach循环
start = System.nanoTime();
for (int num : list) {
// 仅遍历
}
foreachTime = System.nanoTime() - start;
结果:foreach快约30%(因优化了迭代器访问)
五、常见陷阱与解决方案
1. foreach中修改集合
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
// 错误示范(抛出ConcurrentModificationException)
for (String s : list) {
if ("B".equals(s)) {
list.remove(s); // ❌ 遍历时修改
}
}
// 正确做法1:使用迭代器
Iterator<String> it = list.iterator();
while (it.hasNext()) {
if ("B".equals(it.next())) {
it.remove(); // ✅ 安全删除
}
}
// 正确做法2:使用for循环
for (int i = 0; i < list.size(); i++) {
if ("B".equals(list.get(i))) {
list.remove(i--); // 调整索引
}
}
2. 基本类型数组问题
int[] nums = {1, 2, 3};
for (int num : nums) {
num *= 2; // 不影响原数组!
}
System.out.println(Arrays.toString(nums)); // 输出[1, 2, 3]
六、最佳实践指南
-
默认选择:
- 集合遍历 → foreach
- 数组遍历 → 根据是否需要索引选择
-
性能关键路径:
- 超大型数组 → 传统for
- 链表等随机访问慢的集合 → 绝对用foreach
-
代码规范:
// 好 for (Student s : students) { ... } // 不好(除非需要索引) for (int i = 0; i < students.size(); i++) { ... }
七、Java8的降维打击
场景 | 传统写法 | Java8+改进 |
---|---|---|
遍历 | for(String s : list) | list.forEach(System.out::println) |
带索引 | for(int i=0; i<list.size(); i++) | IntStream.range(0,list.size()).forEach(i->...) |
过滤 | 手动迭代检查 | list.stream().filter(...).collect(...) |
// 更现代的遍历方式
list.forEach(System.out::println);
// 带索引的流式处理
IntStream.range(0, list.size())
.forEach(i -> System.out.println(i + ": " + list.get(i)));
八、常见陷阱警示牌
典型问题:
ConcurrentModificationException
- 基本类型数组修改无效
- LinkedList用for循环get()性能差
九、企业级编码规范建议
-
基础规范:
- 集合遍历优先foreach
- 需要索引时用for
- 修改集合用Iterator
-
性能规范:
// 好:优化数组遍历 for (int i = 0, len = array.length; i < len; i++) // 不好:每次计算size() for (int i = 0; i < list.size(); i++)
-
可读性规范:
// 好:明确表达意图 for (Order order : unpaidOrders) { process(order); } // 不好:索引无实际用途 for (int i = 0; i < orders.size(); i++) { process(orders.get(i)); }
十、循环选择终极指南
结语:循环选择的艺术
🔑 终极选择口诀:
foreach简洁又优雅,集合遍历首选它;
for循环更全能,索引控制它最行;
数组遍历看场景,性能关键for能赢;
迭代修改要小心,迭代器方案最安心!
记住:没有最好的循环,只有最合适的场景!