标签:对象 继承 oar object lan 索引 const tool href
转发:http://blog.csdn.net/u011392897/article/details/54562596
for-each循环是jdk1.5引入的新的语法功能。并不是所有东西都可以使用这个循环的。可以看下Iterable接口的注释,它说明了除了数组外,其他类想要使用for-each循环必须实现这个接口。这一点表明除了数组外的for-each可能底层是由迭代器实现的。
Iterable接口在1.8之前只有一个方法,Iterator<T> iterator(),此方法返回一个迭代器。由于更早出现的Collection接口中早就有了这个同样的方法,所以只需要让Collection接口继承Iterable接口,基于Collection的集合类就可以不做任何更改就使用for-each循环。
对于数组,因为数组不实现Iterable接口,它的for-each实现原理应该和Collection不一样。
下面就通过分析下不同方式编译后的字节码,简单研究下for-each的的底层原理。
一、数组的for-each
下面是的两个很简单的类,可以看出它们的功能是一样的。Java环境使用的是jdk1.8_111。
- package iter;
-
- public class TestArray {
- public static void main(String[] args) {
-
- long[] a = {2L, 3L, 5L};
- for (long i : a) {
- System.err.println(i);
- }
- }
- }
- package iter;
-
- public class TestArrayFor {
- public static void main(String[] args) {
-
- long[] a = {2L, 3L, 5L};
- for (int i = 0, len = a.length; i < len; i++) {
- System.err.println(a[i]);
- }
- }
- }
TestArray使用for-each,TestArrayFor使用传统for循环,使用long数组是为了字节码中好区分int/long。
用javap -c看下两个类的字节码操作,保存成了文本,具体情况如下。
- Compiled from "TestArray.java"
- public class iter.TestArray {
- public iter.TestArray();
- Code:
- 0: aload_0
- 1: invokespecial #8
- 4: return
-
- public static void main(java.lang.String[]);
- Code:
- 0: iconst_3
- 1: newarray long
- 3: dup
- 4: iconst_0
- 5: ldc2_w #16
- 8: lastore
- 9: dup
- 10: iconst_1
- 11: ldc2_w #18
- 14: lastore
- 15: dup
- 16: iconst_2
- 17: ldc2_w #20
- 20: lastore
- 21: astore_1
- 22: aload_1
- 23: dup
- 24: astore 6
- 26: arraylength
- 27: istore 5
- 29: iconst_0
- 30: istore 4
- 32: goto 51
- 35: aload 6
- 37: iload 4
- 39: laload
- 40: lstore_2
- 41: getstatic #22
- 44: lload_2
- 45: invokevirtual #28
- 48: iinc 4, 1
- 51: iload 4
- 53: iload 5
- 55: if_icmplt 35
- 58: return
- }
- Compiled from "TestArrayFor.java"
- public class iter.TestArrayFor {
- public iter.TestArrayFor();
- Code:
- 0: aload_0
- 1: invokespecial #8
- 4: return
-
- public static void main(java.lang.String[]);
- Code:
- 0: iconst_3
- 1: newarray long
- 3: dup
- 4: iconst_0
- 5: ldc2_w #16
- 8: lastore
- 9: dup
- 10: iconst_1
- 11: ldc2_w #18
- 14: lastore
- 15: dup
- 16: iconst_2
- 17: ldc2_w #20
- 20: lastore
- 21: astore_1
- 22: iconst_0
- 23: istore_2
- 24: aload_1
- 25: arraylength
- 26: istore_3
- 27: goto 42
- 30: getstatic #22
- 33: aload_1
- 34: iload_2
- 35: laload
- 36: invokevirtual #28
- 39: iinc 2, 1
- 42: iload_2
- 43: iload_3
- 44: if_icmplt 30
- 47: return
- }
本人对照下字节码指令表,简单翻译了以下,都写在上面,还算是比较清楚。/**/中的就是本人的注释,//开头的是字节码自带的信息,这些信息不能完全算是注释吧,可以算是对字节码中出现的常量的一种直白翻译,让你看得懂这些常量代表什么。
通过编译后的字节码可以看出,数组的for-each和普通的for循环底层原理是一样的,都是用的普通for循环的那一套。数组的for-each比普通for循环多一点点操作,理论上是要慢一点点,这个暂时也不知道是为什么。这也是语法糖的一些代价,语法越简单,反而越不好进行底层优化。不过这个慢一点那真是一点,在循环体比较复杂时,这个差距就更小了,所以基本上可以认为这两种方式效率一样。实际中根据自己的情况选择,如果需要显式使用下标,就用传统for循环,其他的都可以使用for-each循环。
二、Collection的for-each
还是先贴两段简单的对比的代码,代码逻辑一样。Java环境使用的是jdk1.8_111。
- package iter;
-
- import java.util.ArrayList;
- import java.util.List;
-
- public class TestFor {
- public static void main(String[] args) {
- List<String> listA = new ArrayList<String>();
- for(String str : listA) {
- System.err.println(str);
- }
- }
- }
- package iter;
-
- import java.util.ArrayList;
- import java.util.Iterator;
- import java.util.List;
-
- public class TestIter {
- public static void main(String[] args) {
- List<String> listA = new ArrayList<String>();
- for (Iterator<String> iter = listA.iterator(); iter.hasNext();) {
- String s = iter.next();
- System.err.println(s);
- }
- }
- }
TestFor是for-each循环,TestIter是使用迭代器循环。
还是跟数组的一样分析,贴下编译后的字节码。
- Compiled from "TestFor.java"
- public class iter.TestFor {
- public iter.TestFor();
- Code:
- 0: aload_0
- 1: invokespecial #8
- 4: return
-
- public static void main(java.lang.String[]);
- Code:
- 0: new #16
- 3: dup
- 4: invokespecial #18
- 7: astore_1
- 8: aload_1
- 9: invokeinterface #19, 1
- 14: astore_3
- 15: goto 35
- 18: aload_3
- 19: invokeinterface #25, 1
- 24: checkcast #31
- 27: astore_2
- 28: getstatic #33
- 31: aload_2
- 32: invokevirtual #39
- 35: aload_3
- 36: invokeinterface #45, 1
- 41: ifne 18
- 44: return
- }
- Compiled from "TestIter.java"
- public class iter.TestIter {
- public iter.TestIter();
- Code:
- 0: aload_0
- 1: invokespecial #8
- 4: return
-
- public static void main(java.lang.String[]);
- Code:
- 0: new #16
- 3: dup
- 4: invokespecial #18
- 7: astore_1
- 8: aload_1
- 9: invokeinterface #19, 1
- 14: astore_2
- 15: goto 35
- 18: aload_2
- 19: invokeinterface #25, 1
- 24: checkcast #31
- 27: astore_3
- 28: getstatic #33
- 31: aload_3
- 32: invokevirtual #39
- 35: aload_2
- 36: invokeinterface #45, 1
- 41: ifne 18
- 44: return
- }
这两段字节码中自带的注释很多,基本上看得懂,就不添加注释了。
两段字节码除了几个变量保存在线程的当前栈帧的局部变量表的索引(astore_n,这个n就是索引)不一样外,其余的都是一模一样的。不排除某次编译后连那几个索引值也一样,那就真一模一样了。字节码自带的注释都说了,Collection的for-each底层也是使用迭代器来实现的,两种方式可以说是完全等价的。
对于实现了RandomAccess接口的实现类,因为它们的随机访问操作的时间复杂度为O(1),大多数情况使用传统for循环会比用迭代器循环(这里的迭代器也可以用for-each替换,上面说了它们底层整体是一样的)要快。至于这一点是为什么,可以看下ArrayList的源码。它的迭代器虽然也是通过下标直接访问elementData数组,但是迭代器多了很多方法调用以及其他的额外操作,现在很多编译器cpu也都会对传统for循环进行特别的优化,在这个层面十几个指令的差别就很大了,这些因素加在一起导致RandomAccess的迭代器比传统for循环要慢一些。对于ArrayList这种,在cpu密集型的应用中应该只使用传统for循环,在循环体执行时间比较长的应用中,传统for循环和迭代器循环的差别就很小了,这时候使用迭代器(for-each循环)也不会明显降低执行效率。
参考:
1、https://docs.oracle.com/javase/8/docs/technotes/guides/language/foreach.html
2、Java虚拟机规范(Java SE 8)
从字节码看Java中for-each循环(增强for循环)实现原理
标签:对象 继承 oar object lan 索引 const tool href
原文地址:http://www.cnblogs.com/huyong0401/p/7094820.html