Decompilation for Foreach Syntax Sugar


 

Decompilation for Foreach Syntax Sugar

通过反编译理解Java foreach 语法糖

前言

最近一直在看TiJ,在持有对象那章,Bruce Eckel说到Collection的内容可以用foreach遍历,原因是Java SE%引入了Iterable接口,该接口包含一个能够产生Iterator的iterator方法,并且Iterable接口被foreach用来在序列中移动。因此任何Iterable的实现类都可以用于foreach语句。那么我们在尝着这一语法糖的甜头时,JDK底层帮我们做了哪些事情呢?

实验

环境:IntelliJ IDEA 2018.1、jdk1.7.0_80

下面例子的Java代码中使用了两个foreach分别遍历数组和ArrayList:

package reversecompile;

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

public class TestForEach {
    public static void main(String[] args) {
        String[] arr = "I am a coder".split(" ");
        List<String> strings = new ArrayList<>();
        for (String s : arr){
            strings.add(s);
            System.out.println(s);
        }
        for (String s : strings){
            System.out.println(s);
        }
    }
}

通过IDEA反编译后的·TestForEach.class

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package reversecompile;

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

public class TestForEach {
    public TestForEach() {
    }

    public static void main(String[] args) {
        String[] arr = "I am a coder".split(" ");
        List<String> strings = new ArrayList();
        String[] arr$ = arr;
        int len$ = arr.length;

        for(int i$ = 0; i$ < len$; ++i$) {
            String s = arr$[i$];
            strings.add(s);
            System.out.println(s);
        }

        Iterator i$ = strings.iterator();

        while(i$.hasNext()) {
            String s = (String)i$.next();
            System.out.println(s);
        }
    }
}

可以看到JDK1.7在遍历数组时,先获取数组长度,然后将原来foreach变成了普通的for循环,原因是数组没有实现Iterable接口;而在遍历ArrayList时则是先获取该集合对象的Iterator 对象,然后再通过这个迭代器去遍历集合的元素。

为了验证任何Iterable的实现类都可以用于foreach语句,我们可以试着创建一个Iterable的实现类,并且实现Iterable的iterator()方法,为了得到一个Iterator<对象,在iterator()方法方法中直接返回一个Iterator的匿名实现类,在其中实现Iterator接口的方法,由于集合操作的对象是数组,鉴于数组删除元素复杂度大所以在这个匿名内部类中并没有实现remove方法。

package reversecompile;

import java.util.Iterator;

public class IterableClass implements Iterable<String> {

    private String[] arr;

    public IterableClass(String[] arr) {
        this.arr = arr;
    }

    @Override
    public Iterator<String> iterator() {
        return new Iterator<String>() { //返回Iterator匿名实现类
            private int index = 0;
            
            @Override
            public boolean hasNext() {
                return index < arr.length;
            }

            @Override
            public String next() {
                return arr[index ++];
            }

            @Override
            public void remove() {
               //TODO
            }
        };
    }
}

在测试类中使用foreach语句,因为IterableClass持有的对象类型为String,所以直接在foreach使用for (String s : iterableClass)

package reversecompile;

public class TestForEach {
    public static void main(String[] args) {
        String[] arr = "I am a coder".split(" ");
        IterableClass iterableClass = new IterableClass(arr);

        for (String s : iterableClass){
            System.out.println(s);
        }
    }
}

使用IDEA自带反编译工具得到的TestForEach.class文件

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package reversecompile;

import java.util.Iterator;

public class TestForEach {
    public TestForEach() {
    }

    public static void main(String[] args) {
        String[] arr = "I am a coder".split(" ");
        IterableClass iterableClass = new IterableClass(arr);
        Iterator i$ = iterableClass.iterator();

        while(i$.hasNext()) {
            String s = (String)i$.next();
            System.out.println(s);
        }
    }
}

同样地JDK也是通过IterableClass的对象获取一个Iterator对象,然后通过该迭代器遍历IterableClass中持有的元素。

题外话:JDK1.7操作String数组时并没有使用原来的引用,而是通过复制原引用去操作数组对象(为避免离题原因待深入)