foreach 语法糖的原理

foreach 语法糖的原理

foreach 是 Java 提供的语法糖,为了方便程序员使用而添加的。本文提供简单的例子说明一下原理。

集合 Collection

测试代码

package me.zhao;

import java.util.ArrayList;
import java.util.List;

public class ListTest {

    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        for (String s : list) {
            s.length();
        }
    }
}

反编译后的测试代码

package me.zhao;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class ListTest {
    public ListTest() {
    }

    public static void main(String[] args) {
        List<String> list = new ArrayList();
        Iterator var2 = list.iterator();

        while(var2.hasNext()) {
            String s = (String)var2.next();
            s.length();
        }
    }
}

可以看到 foreach 的使用被替换成了使用 Iterator

  1. 调用 List.iterator() 获取一个迭代器 Iterator
  2. 调用 IteratorhasNext() 方法
  3. 调用 Iteratornext() 方法

第一步之所以能通过 List.iterator() 获取一个迭代器 Iterator,是因为 List 或者说 Collection 继承里 Iterable 接口,注意是 Iterable ,不是 IteratorIterable 接口中定义了一个方法 Iterator<T> iterator() 用于返回一个迭代器。Iterable 的 javadoc 写到Implementing this interface allows an object to be the target of the “for-each loop” statement,意思是实现了此接口的对象可以被用于 “for-each loop”。

字节码

IDEA可查看字节码:菜单 View -> Show Bytecode

// class version 52.0 (52)
// access flags 0x21
public class me/zhao/ListTest {

  // compiled from: ListTest.java

  // access flags 0x1
  public <init>()V
   L0
    LINENUMBER 6 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
    RETURN
   L1
    LOCALVARIABLE this Lme/zhao/ListTest; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x9
  public static main([Ljava/lang/String;)V
   L0
    LINENUMBER 9 L0
    NEW java/util/ArrayList
    DUP
    INVOKESPECIAL java/util/ArrayList.<init> ()V
    ASTORE 1
   L1
    LINENUMBER 10 L1
    ALOAD 1
    INVOKEINTERFACE java/util/List.iterator ()Ljava/util/Iterator; (itf)
    ASTORE 2
   L2
   FRAME APPEND [java/util/List java/util/Iterator]
    ALOAD 2
    INVOKEINTERFACE java/util/Iterator.hasNext ()Z (itf)
    IFEQ L3
    ALOAD 2
    INVOKEINTERFACE java/util/Iterator.next ()Ljava/lang/Object; (itf)
    CHECKCAST java/lang/String
    ASTORE 3
   L4
    LINENUMBER 11 L4
    ALOAD 3
    INVOKEVIRTUAL java/lang/String.length ()I
    POP
   L5
    LINENUMBER 12 L5
    GOTO L2
   L3
    LINENUMBER 13 L3
   FRAME CHOP 1
    RETURN
   L6
    LOCALVARIABLE s Ljava/lang/String; L4 L5 3
    LOCALVARIABLE args [Ljava/lang/String; L0 L6 0
    LOCALVARIABLE list Ljava/util/List; L1 L6 1
    // signature Ljava/util/List<Ljava/lang/String;>;
    // declaration: list extends java.util.List<java.lang.String>
    MAXSTACK = 2
    MAXLOCALS = 4
}

通过字节码同样可以看出以上的三步:30、35、38行。

这里还有另一个有意思的事情,在反编译后代码的第 16 行 String s = (String)var2.next(); 有一个强制类型转换;在字节码的第 45 行 CHECKCAST java/lang/String 有一个类型检查,不通过的话会抛出异常 ClassCastException。这些应该都和 Java 的擦除式泛型有关。

数组

测试代码

package me.zhao;

public class ArrayTest {

    public static void main(String[] args) {
        String[] arr = {};
        for (String s : arr) {
            System.out.println(s);
        }
    }
}

反编译后的测试代码

package me.zhao;

public class ArrayTest {
    public ArrayTest() {
    }

    public static void main(String[] args) {
        String[] arr = new String[0];
        String[] var2 = arr;
        int var3 = arr.length;

        for(int var4 = 0; var4 < var3; ++var4) {
            String s = var2[var4];
            System.out.println(s);
        }
    }
}

可以看出 Java 对数组的处理比较简单,就是将其转换成了普通的 for 循环。

总结

  • 对于集合类,更准确的说是实现了 Iterable 接口的类,会被转换成 Iterator
  • 对于数组而言,会转换成普通的 for 循环

参考

展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客
应支付0元
点击重新获取
扫码支付

支付成功即可阅读