Java中for-each的工作原理:从新手到专家的实例解析

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

引言:为什么for-each成为Java中的“循环之王”?

\n\n

如果您至少写过一周的Java代码,您一定遇到过for (Type var : collection)这种结构。这种简洁的语法被称为for-each循环(或增强型for循环)。它早在Java 5(2004年)中就出现了,并迅速成为开发者的最爱。

\n\n

但是,您是否知道当JVM遇到这种结构时,底层发生了什么?为什么for-each对于数组来说可能比普通for循环慢?为什么即使您没有删除任何内容,它有时也会抛出ConcurrentModificationException?

\n\n

在本文中,我们将从字节码层面深入分析Java中for-each的工作原理,与经典循环进行比较,并学习如何避免常见的陷阱。让我们开始吧!

\n\n

语法与初步了解

\n\n

基本语法如下:

\n\n
for (元素类型 变量名 : 集合或数组) {\n    // 循环体\n}
\n\n

使用ArrayList的示例:

\n\n
List<String> names = Arrays.asList("安娜", "鲍里斯", "维克多");\n\nfor (String name : names) {\n    System.out.println("你好, " + name + "!");\n}
\n\n

输出:

\n\n
你好, 安娜!\n你好, 鲍里斯!\n你好, 维克多!
\n\n

很简单,对吧?但在这简单的背后,隐藏着两种完全不同的机制——一种用于数组,另一种用于集合。

\n\n

for-each如何与数组一起工作?

\n\n

当您为数组编写for-each时,Java编译器会将其转换为一个带有索引的普通for循环。没有任何魔法。

\n\n

您的代码:

\n\n
int[] numbers = {10, 20, 30};\n\nfor (int num : numbers) {\n    System.out.println(num);\n}
\n\n

编译后(反编译字节码——您会看到):

\n\n
int[] numbers = new int[]{10, 20, 30};\nfor (int i = 0; i < numbers.length; i++) {\n    int num = numbers[i];\n    System.out.println(num);\n}
\n\n

重要结论:对于数组,for-each和普通for循环在性能上是相同的。没有迭代器的额外开销。

\n\n

for-each如何与集合(List, Set)一起工作?

\n\n

这里开始变得有趣了。对于集合,for-each使用迭代器。

\n\n

您的代码:

\n\n
List<String> list = new ArrayList<>();\nlist.add("Java");\nlist.add("Python");\nlist.add("C++");\n\nfor (String lang : list) {\n    System.out.println(lang);\n}
\n\n

编译器将其转换为:

\n\n
List<String> list = new ArrayList<>();\nlist.add("Java");\nlist.add("Python");\nlist.add("C++");\n\nfor (Iterator<String> it = list.iterator(); it.hasNext(); ) {\n    String lang = it.next();\n    System.out.println(lang);\n}
\n\n

也就是说:

\n
    \n
  • 调用list.iterator()——获取迭代器。
  • \n
  • 条件为it.hasNext()。
  • \n
  • 循环体内为it.next()。
  • \n
  • 变量lang由next()的结果初始化。
  • \n
\n\n

这在实践中意味着什么?

\n
    \n
  • 不能在for-each内部通过list.remove()删除元素——您会得到ConcurrentModificationException。请使用it.remove()或普通for循环。
  • \n
  • 不能在迭代期间修改集合(添加/删除)——同样会抛出该错误。
  • \n
  • 性能:对于ArrayList,与普通for循环的差异很小(创建迭代器对象的开销)。对于LinkedList——for-each比for (int i = 0; i < list.size(); i++)快得多,因为LinkedList的get(i)每次都是O(n)复杂度!
  • \n
\n\n

Map呢?遍历键值对

\n\n

Map不直接实现Iterable,因此for-each

博客

书籍推荐