foreach用错了?

foreach用错了?

错误认识

学了那么久的java了,我们大部分人对foreach的认识就是:只要遍历就用foreach语法,但事实上真的是这样么?

for与foreach简单对比

for:可以知道元素的位标、可以通过位标修改元素内容。
foreach:不能修改元素内容、“只读”操作、实质上使用Iterator遍历、但不能像Iterator一样直接用next()或hasNext()控制。

深入foreach

我们可以通过反编译Class文件,查看字节码,了解底层的实现方式。

反编译方式:

javap -verbose MainClass > t.txt

在命令行执行该命令,在相同路径下生成t.txt文件,文件中存放的就是字节码。

部分JVM字节码解析:

字节码解释
aastore将栈顶引用型数值存入指定数组的指定索引位置
aload_0将第一个局部变量的引用值压进栈
anewarray栈顶数值作为数组长度,创建引用型数组,栈顶数值出栈,数组引用进栈
arraylength栈顶数组引用出栈,数组长度进栈
astore_1将栈顶引用数值存入当前frame(域)第二个局部变量中
dup复制栈顶数值
iconst将int型常量值压进栈
iconst_0将int类型0值压进栈
ldc将int、float、String常量值从常量池推至(push)栈顶

示例

对数组使用foreach语法

java代码

public class MainClass {
    public static final void main(String[] args) {
        String[] stringArray = new String[]{"test > 1", "test > 2"};

        for (String str : stringArray) {
            System.out.println(str);
        }
    }
}

反编译出的字节码

Classfile /C:/Users/Administrator/Desktop/MainClass.class
  Last modified 2016-12-8; size 579 bytes
  MD5 checksum 716231b8e350a3d39604b15922cfb8c1
  Compiled from "MainClass.java"
public class MainClass
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #8.#19         // java/lang/Object."<init>":()V
   #2 = Class              #20            // java/lang/String
   #3 = String             #21            // test > 1
   #4 = String             #22            // test > 2
   #5 = Fieldref           #23.#24        // java/lang/System.out:Ljava/io/PrintStream;
   #6 = Methodref          #25.#26        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #7 = Class              #27            // MainClass
   #8 = Class              #28            // java/lang/Object
   #9 = Utf8               <init>
  #10 = Utf8               ()V
  #11 = Utf8               Code
  #12 = Utf8               LineNumberTable
  #13 = Utf8               main
  #14 = Utf8               ([Ljava/lang/String;)V
  #15 = Utf8               StackMapTable
  #16 = Class              #29            // "[Ljava/lang/String;"
  #17 = Utf8               SourceFile
  #18 = Utf8               MainClass.java
  #19 = NameAndType        #9:#10         // "<init>":()V
  #20 = Utf8               java/lang/String
  #21 = Utf8               test > 1
  #22 = Utf8               test > 2
  #23 = Class              #30            // java/lang/System
  #24 = NameAndType        #31:#32        // out:Ljava/io/PrintStream;
  #25 = Class              #33            // java/io/PrintStream
  #26 = NameAndType        #34:#35        // println:(Ljava/lang/String;)V
  #27 = Utf8               MainClass
  #28 = Utf8               java/lang/Object
  #29 = Utf8               [Ljava/lang/String;
  #30 = Utf8               java/lang/System
  #31 = Utf8               out
  #32 = Utf8               Ljava/io/PrintStream;
  #33 = Utf8               java/io/PrintStream
  #34 = Utf8               println
  #35 = Utf8               (Ljava/lang/String;)V
{
  public MainClass();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 1: 0

  public static final void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
    Code:
      stack=4, locals=6, args_size=1
         0: iconst_2
         1: anewarray     #2                  // class java/lang/String
         4: dup
         5: iconst_0
         6: ldc           #3                  // String test > 1
         8: aastore
         9: dup
        10: iconst_1
        11: ldc           #4                  // String test > 2
        13: aastore
        14: astore_1
        15: aload_1
        16: astore_2
        17: aload_2
        18: arraylength
        19: istore_3
        20: iconst_0
        21: istore        4
        23: iload         4
        25: iload_3
        26: if_icmpge     49
        29: aload_2
        30: iload         4
        32: aaload
        33: astore        5
        35: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
        38: aload         5
        40: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        43: iinc          4, 1
        46: goto          23
        49: return
      LineNumberTable:
        line 3: 0
        line 5: 15
        line 6: 35
        line 5: 43
        line 8: 49
      StackMapTable: number_of_entries = 2
        frame_type = 255 /* full_frame */
          offset_delta = 23
          locals = [ class "[Ljava/lang/String;", class "[Ljava/lang/String;", class "[Ljava/lang/String;", int, int ]
          stack = []
        frame_type = 248 /* chop */
          offset_delta = 25
}
SourceFile: "MainClass.java"

解析:我们只用看main函数里面的东西就好了,对照前面的字节码解析可以大概知道遍历数组,只是简单的进栈出栈。

等同代码:

for (int len = stringArray.length, i = 0; i < len; ++i) {
        String str = stringArray[i];
        System.out.println(str);
}
对List使用foreach语法

java代码

public class MainClass {
    public static final void main(String[] args) {
        List<String> stringList = new ArrayList<>();
        stringList.add("test > 1");
        stringList.add("test > 2");
        for (String str : stringList) {
            System.out.println(str);
        }
    }
}

字节码

Classfile /C:/Users/Administrator/Desktop/MainClass.class
  Last modified 2016-12-8; size 798 bytes
  MD5 checksum 9e8aab2da24e08f2a9a5b4de4ced5e3d
  Compiled from "MainClass.java"
public class MainClass
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #14.#26        // java/lang/Object."<init>":()V
   #2 = Class              #27            // java/util/ArrayList
   #3 = Methodref          #2.#26         // java/util/ArrayList."<init>":()V
   #4 = String             #28            // test > 1
   #5 = InterfaceMethodref #29.#30        // java/util/List.add:(Ljava/lang/Object;)Z
   #6 = String             #31            // test > 2
   #7 = InterfaceMethodref #29.#32        // java/util/List.iterator:()Ljava/util/Iterator;
   #8 = InterfaceMethodref #33.#34        // java/util/Iterator.hasNext:()Z
   #9 = InterfaceMethodref #33.#35        // java/util/Iterator.next:()Ljava/lang/Object;
  #10 = Class              #36            // java/lang/String
  #11 = Fieldref           #37.#38        // java/lang/System.out:Ljava/io/PrintStream;
  #12 = Methodref          #39.#40        // java/io/PrintStream.println:(Ljava/lang/String;)V
  #13 = Class              #41            // MainClass
  #14 = Class              #42            // java/lang/Object
  #15 = Utf8               <init>
  #16 = Utf8               ()V
  #17 = Utf8               Code
  #18 = Utf8               LineNumberTable
  #19 = Utf8               main
  #20 = Utf8               ([Ljava/lang/String;)V
  #21 = Utf8               StackMapTable
  #22 = Class              #43            // java/util/List
  #23 = Class              #44            // java/util/Iterator
  #24 = Utf8               SourceFile
  #25 = Utf8               MainClass.java
  #26 = NameAndType        #15:#16        // "<init>":()V
  #27 = Utf8               java/util/ArrayList
  #28 = Utf8               test > 1
  #29 = Class              #43            // java/util/List
  #30 = NameAndType        #45:#46        // add:(Ljava/lang/Object;)Z
  #31 = Utf8               test > 2
  #32 = NameAndType        #47:#48        // iterator:()Ljava/util/Iterator;
  #33 = Class              #44            // java/util/Iterator
  #34 = NameAndType        #49:#50        // hasNext:()Z
  #35 = NameAndType        #51:#52        // next:()Ljava/lang/Object;
  #36 = Utf8               java/lang/String
  #37 = Class              #53            // java/lang/System
  #38 = NameAndType        #54:#55        // out:Ljava/io/PrintStream;
  #39 = Class              #56            // java/io/PrintStream
  #40 = NameAndType        #57:#58        // println:(Ljava/lang/String;)V
  #41 = Utf8               MainClass
  #42 = Utf8               java/lang/Object
  #43 = Utf8               java/util/List
  #44 = Utf8               java/util/Iterator
  #45 = Utf8               add
  #46 = Utf8               (Ljava/lang/Object;)Z
  #47 = Utf8               iterator
  #48 = Utf8               ()Ljava/util/Iterator;
  #49 = Utf8               hasNext
  #50 = Utf8               ()Z
  #51 = Utf8               next
  #52 = Utf8               ()Ljava/lang/Object;
  #53 = Utf8               java/lang/System
  #54 = Utf8               out
  #55 = Utf8               Ljava/io/PrintStream;
  #56 = Utf8               java/io/PrintStream
  #57 = Utf8               println
  #58 = Utf8               (Ljava/lang/String;)V
{
  public MainClass();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 4: 0

  public static final void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
    Code:
      stack=2, locals=4, args_size=1
         0: new           #2                  // class java/util/ArrayList
         3: dup
         4: invokespecial #3                  // Method java/util/ArrayList."<init>":()V
         7: astore_1
         8: aload_1
         9: ldc           #4                  // String test > 1
        11: invokeinterface #5,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
        16: pop
        17: aload_1
        18: ldc           #6                  // String test > 2
        20: invokeinterface #5,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
        25: pop
        26: aload_1
        27: invokeinterface #7,  1            // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;
        32: astore_2
        33: aload_2
        34: invokeinterface #8,  1            // InterfaceMethod java/util/Iterator.hasNext:()Z
        39: ifeq          62
        42: aload_2
        43: invokeinterface #9,  1            // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
        48: checkcast     #10                 // class java/lang/String
        51: astore_3
        52: getstatic     #11                 // Field java/lang/System.out:Ljava/io/PrintStream;
        55: aload_3
        56: invokevirtual #12                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        59: goto          33
        62: return
      LineNumberTable:
        line 6: 0
        line 7: 8
        line 8: 17
        line 9: 26
        line 10: 52
        line 11: 59
        line 12: 62
      StackMapTable: number_of_entries = 2
        frame_type = 253 /* append */
          offset_delta = 33
          locals = [ class java/util/List, class java/util/Iterator ]
        frame_type = 250 /* chop */
          offset_delta = 28
}
SourceFile: "MainClass.java"

解析:可以发现注释中有Iterator。所以对ArrayList作遍历操作,实质上是使用Iterator来遍历。

等同代码:

for (Iterator<String> i = stringList.iterator(); i.hasNext();) {
        String str = i.next();
        System.out.println(str);
}

小结

对数组使用foreach语法,实质上转化成普通的for循环实现。而对List使用foreach语法,实质上为Iterator实现,与普通for循环实现完全不同。

再深入

而到这里,查看ArrayList源码,会发现ArrayList实现了RandomAccess接口,我们再查看一下RandomAccess源码,会发现以下注释。

Marker interface used by List implementations to indicate that they support fast (generally constant time) random access. The primary purpose of this interface is to allow generic algorithms to alter their behavior to provide good performance when applied to either random or sequential access lists.

List的实现使用的标记接口,指明了它们支持快速随机访问。该接口的最初目的是:当运用到随机访问和连续访问List时,为了提高性能,允许一般的算法改变其行为。

The best algorithms for manipulating random access lists (such as ArrayList) can produce quadratic behavior when applied to sequential access lists (such as LinkedList). Generic list algorithms are encouraged to check whether the given list is an instanceof this interface before applying an algorithm that would provide poor performance if it were applied to a sequential access list, and to alter their behavior if necessary to guarantee acceptable performance.

一个在随机访问List的最佳算法运用到连续访问List中会产生二次项行为(???)推荐一般的List算法在运用前根据判断List是否instanceof该接口,来做不同的处理,以保证良好的性能。

It is recognized that the distinction between random and sequential access is often fuzzy. For example, some List implementations provide asymptotically linear access times if they get huge, but constant access times in practice. Such a List implementation should generally implement this interface. As a rule of thumb, a List implementation should implement this interface if, for typical instances of the class, this loop:
for (int i=0, n=list.size(); i < n; i++) list.get(i);

runs faster than this loop:
for (Iterator i=list.iterator(); i.hasNext(); ) i.next();

现在认识到随机访问和连续访问的界限是模糊的。例如,一些List的实现如果变的非常大的时候就会表现出渐进线性访问时间,而实施中却是恒定不变的。这样的List实现类应实现该接口。使用for遍历实现改接口的List的效率比foreach快很多。

所以,实现了RandomAccess的List(例如ArrayList),在遍历的时候使用for循环而不使用foreach。而对于没有实现RandomAccess的List(例如LinkedList),遍历使用Iterator更有效率一点。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值