您的位置:首页 > 编程语言 > Java开发

Java增强for循环--foreach深入探讨

2018-03-09 17:02 465 查看
首先我们来认识Java中的foreach,foreach也被称为增强的for循环

我们来看一下他的基本用法

对于基本数据类型的数组遍历有如下用法:

int[] arr = new int[100];
// foreach写法
for (int i : arr) {
System.out.println(i);
}


可以看出,对于基本数据类型,增强的for循环用法很简单,
for (int i : arr)
for
后跟的括号里,是
类型  变量名
,然后跟
:数组名
,然后就可以使用变量名进行遍历。

而对于Java集合类的遍历,则和基本数据类型的数组类似

LinkedList<Integer>list = new LinkedList<>();

for(int i=0;i<100;i++) {
list.add(i);
}
for (Integer integer : list) {
System.out.println(integer);
}


只不过类型变为了包装类,其他用法和以上一模一样。

了解完了其基本用法,我们来深挖下其背后的实现原理

有很多教材上说过,尽量使用增强的for循环,因为它更快速更安全,而又有些书中写到,编译器会自动把foreach循环变为for循环,那么问题来了,如果只是单纯的转换为for循环,为什么更安全性能更好呢?

我们来亲自看一下编译器到底做了什么,有下面这一段代码

int[] arr = new int[100];

for (int i : arr) {
//          System.out.println(i);
}


javac编译以后得到字节码

0: bipush        100
2: newarray       int
4: astore_1
5: aload_1
6: astore_2
7: aload_2
8: arraylength
9: istore_3
10: iconst_0
11: istore        4
13: iload         4
15: iload_3
16: if_icmpge     31
19: aload_2
20: iload         4
22: iaload
23: istore        5
25: iinc          4, 1
28: goto          13
31: return


可以看到JVM首先会创建一个长度为100的
int
数组,然后有意思的事情来了,

8: arraylength
9: istore_3


先取了数组长度

10: iconst_0
11: istore        4
13: iload         4
15: iload_3
16: if_icmpge     31


然后有初始化了一个
int值等于0
,和
arraylength
比较大小,如果大于或等于他则跳转至31行

25: iinc          4, 1
28: goto          13


最后初始化的
int
值自增1,然后跳转至13行

很明显,这是一个for循环,而且是我们最常用的这种遍历形式,就和下面段一模一样

for (int i = 0; i < arr.length; i++) {
// do something
}


也就是说对于基本类型数组的foreach循环,编译器最后会把它变为一个for循环,而且保障了其边界安全性,这也就是某些书上说编译器会把增强的for循环编译称为for循环的原因。

但是对于集合了的foreach循环,编译器会采用更优的一种手段。

LinkedList<Integer> list = new LinkedList<>();
for (Integer integer : list) {
}


经过javac编译后可以得的:

0: new           #2                  // class java/util/LinkedList
3: dup
4: invokespecial #3                  // Method java/util/LinkedList."<init>":()V
7: astore_1
8: aload_1
9: invokevirtual #4                  // Method java/util/LinkedList.iterator:()Ljava/util/Iterator;
12: astore_2
13: aload_2
14: invokeinterface #5,  1            // InterfaceMethod java/util/Iterator.hasNext:()Z
19: ifeq          35
22: aload_2
23: invokeinterface #6,  1            // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
28: checkcast     #7                  // class java/lang/Integer
31: astore_3
32: goto          13
35: return


可以看到,前几行字节码依旧是JVM创建一个
LinkedList
对象

然后我们发现:

9: invokevirtual #4                  // Method java/util/LinkedList.iterator:()Ljava/util/Iterator;


编译器自动调用了
LinkedList.iterator()
方法
,得到了一个
Iterator<Integer>
对象的实例,并且把它存储在第三个本地变量。

13: aload_2
14: invokeinterface #5,  1            // InterfaceMethod java/util/Iterator.hasNext:()Z
19: ifeq          35


这里编译器取出第三个本地变量(
Iterator<Integer>
对象),然后调用了它的
hasNext()
方法
,如果等于0(其实就是boolean的false),则跳转到35行

23: invokeinterface #6,  1            // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
28: checkcast     #7                  // class java/lang/Integer
31: astore_3
32: goto          13


然后编译器又调用了
Iterator<Integer>
对象的
next()
方法,得到了一个
Integer
对象并检查其类型,跳转至13行。


可以看出这也是一个循环,如果转化成对应的JAVA代码如下:

for(Iterator<Integer> I = list.iterator();I.hasNext();) {
I.next();
}


也就是说,对于集合类的增强for循环,编译器是调用了其中实现的
Iterator
类中的方法,这样做的好处是,对于性能更高,以
ArrayList
LinkedList
为例,使用foreach循环遍历,其运行时间都是
O(N)
,否则使用
for
循环配和
get()
方法遍历,
LinkedList
的时间复杂度是
O(N*N)

由此可见,使用增强for循环是很有必要的,对于基本类型的数组类,它确实更安全,而对于集合类的遍历,他更安全更高效。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java jvm 数据结构 循环