for+foreach 详解

目录

简介

  1. foreach 的语法糖实现
  2. for和foreach 遍历的坑

为什么要结合java汇编分析?

因为 java 可以有语法糖,但是汇编绝对不会有语法糖,拨开语法糖的面纱,去看看他们的真面目

for

源码

public class For {

    public static void main(String[] args) {
        List<String> strings = new ArrayList<>();
        strings.add("Brown");

        for (int index = 0;index<strings.size();index++){
            System.out.println(strings.get(index));
        }
    }
}

idea反编译

public class For {
    public For() {
    }

    public static void main(String[] args) {
        List<String> strings = new ArrayList();
        strings.add("Brown");

        for(int index = 0; index < strings.size(); ++index) {
            System.out.println((String)strings.get(index));
        }

    }
}

未能激起丝毫的变化

javap反编译

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=3, 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 Brown
        11: invokeinterface #5,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
        16: pop
        17: iconst_0
        18: istore_2
        19: iload_2
        20: aload_1
        21: invokeinterface #6,  1            // InterfaceMethod java/util/List.size:()I
        26: if_icmpge     51
        29: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
        32: aload_1
        33: iload_2
        34: invokeinterface #8,  2            // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
        39: checkcast     #9                  // class java/lang/String
        42: invokevirtual #10                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        45: iinc          2, 1
        48: goto          19
        51: return

}

逐字逐句翻译为java 代码

    List<String> strings = new ArrayList<>();//0-4
    String str = "Brown";//8-9  
    strings.add(str);//7,11-16
    int i = 0; //17-18
    if(i>= strings.size()){//19-26
        goto return // 跳到方法结束
    }

    PrintStream pw = System.out;//29-32
    Object obj = strings.get(i);//33-34
    String str = (String)obj;//39  checkcast 由强转产生
    pw.println(str);//42
    i++;//45
    goto  if(i>= strings.size())//48  跳转到 i和size 判断的地方
    return // 可以理解为结尾的一个大括号,跳出方法体

由于压根没有goto的语法,所以得翻译成符合java语言规范的代码:

List<String> strings = new ArrayList<>();
strings.add("Brown");
int i = 0;
for(;i<string.size();){
    System.out.println((String)strings.get(i));
    i++;
}

这里可以发现,每次调用 i< strings.size() 的时候. List的size 方法都会被调用一次

foreach

源码

例子:


public class Foreach {

    public static void main(String[] args) {
        List<String> strings = new ArrayList<>();
        strings.add("Brown");

        for (String name:strings){
            System.out.println(name);
        }
    }
}

此时,控制台输出: Brown

反编译

idea反编译

用 idea 自带的反编译

public class Foreach {
    public Foreach() {
    }

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

        while(var2.hasNext()) {
            String name = (String)var2.next();
            System.out.println(name);
        }

    }
}

非常智能的分析出来了,foreach 编译为字节码之后,实际就是调用Iterator进行遍历.

javap反编译

javap -v com/aya/Foreach.class



  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    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 Brown
        11: invokeinterface #5,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
        16: pop
        17: aload_1
        18: invokeinterface #6,  1            // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;
        23: astore_2
        24: aload_2
        25: invokeinterface #7,  1            // InterfaceMethod java/util/Iterator.hasNext:()Z
        30: ifeq          53
        33: aload_2
        34: invokeinterface #8,  1            // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
        39: checkcast     #9                  // class java/lang/String
        42: astore_3
        43: getstatic     #10                 // Field java/lang/System.out:Ljava/io/PrintStream;
        46: aload_3
        47: invokevirtual #11                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        50: goto          24
        53: return

}

从字节码的层面来看,foreach 就是一个while

逐字逐句翻译伪代码如下:

List<String> strings = new ArrayList<>();//0-4, 通过LocalVariableTable 拿到类型和变量名,类型通过LocalVariableTypeTable 获得
strings.add("Brown");//8-16
Iterator iterator = strings.iterator();//17-18
Boolean has = iterator.hasNext() //23-25
if(has){//30   ifeq 栈顶为0 就跳到结束
    String next = (String)iterator.next(); // 33-39 ,  checkcase在强转时产生这个字节码
    System.out.println(next);//42-47
    goto has = iterator.hasNext() //50  由于java 没有goto 语法,所以if判断应该改为while
}
return;

整理伪代码逻辑:

List<String> strings = new ArrayList<>();
strings.add("Brown");
Iterator iterator = strings.iterator();
while(iterator.hasNext()){
    System.out.println((String)iterator.next());
}

idea 分析还是更加智能一些,在不是必要的情况下,用idea 反编译就可以.

这里可以发现,每次遍历的时候,都是调用Iterator 的hasNext和next 方法, iterator只调用了一次

最佳实践

for和foreach 遍历的坑

foreach循环

 public List<String> getList(){
        ArrayList<String> strings = new ArrayList<String>(){
            @Override
            public Iterator<String> iterator() {
                System.out.println("iterator 被调用");
                return super.iterator();
            }
        };

        strings.add("Brown");
        strings.add("Carl");
        return strings;
    }

    @Test
    public void testForEach (){
        for (String name:getList()){
            System.out.println(name);
        }
    }

控制台依次输出:
1. iterator 被调用
2. Brown
3. Carl

for循环

public List<String> getList(){
        System.out.println("getList 被调用");
        ArrayList<String> strings = new ArrayList<String>();
        strings.add("Brown");
        strings.add("Carl");
        return strings;
    }

    @Test
    public void testForEach (){
        for (int i = 0;i<getList().size();i++){
            System.out.println(getList().get(i));
        }
    }

控制台输出:
1. getList 被调用
2. getList 被调用
3. Brown
4. getList 被调用
5. getList 被调用
6. Carl
7. getList 被调用

for循环修复

所以千万不要在for 循环时做方法调用哦.可以写成如下的形式:

    @Test
    public void testForEachFix(){
        List<String> list = getList();
        for (String name: list){
            System.out.println(name);
        }
    }

此时控制台输出:
1. getList 被调用
2. Brown
3. Carl

如果你已经理解字节码汇编指令分析for和foreach 的特性后. 这个问题是不是已经不再疑惑了呢?

Date

开始日期小于等于结束日期



    @Test
    public void testLessEqualDate (){

        Date now = new Date();
        Date start = new Date(now.getTime());
        Date end = new Date(now.getTime());
        end.setDate(end.getDate()+2);

        for (;start.compareTo(end)<=0;start.setDate(start.getDate()+1)){
            System.out.println(start);
        }
    }

开始日期小于结束日期

    @Test
    public void testLessDate (){

        Date now = new Date();
        Date start = new Date(now.getTime());
        Date end = new Date(now.getTime());
        end.setDate(end.getDate()+2);;

        for (;start.before(end);start.setDate(start.getDate()+1)){
            System.out.println(start);
        }
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值