专栏名称: ImportNew
伯乐在线旗下账号,专注Java技术分享,包括Java基础技术、进阶技能、架构设计和Java技术领域动态等。
目录
相关文章推荐
芋道源码  ·  全表update的正确姿势 ·  3 天前  
芋道源码  ·  一种简化操作日志记录的方案) ·  4 天前  
芋道源码  ·  一文讲清各种场景下 Git 如何回退 ·  4 天前  
芋道源码  ·  玩玩大厂都在用的 JavaAgent ·  6 天前  
51好读  ›  专栏  ›  ImportNew

Java 语法糖 1 : 可变长度参数以及 foreach 循环原理

ImportNew  · 公众号  · Java  · 2017-05-27 11:59

正文

(点击上方公众号,可快速关注)


来源:五月的仓颉,

www.cnblogs.com/xrq730/p/4868465.html

如有好文章投稿,请点击 → 这里了解详情


语法糖


接下来几篇文章要开启一个Java语法糖系列,所以首先讲讲什么是语法糖。语法糖是一种几乎每种语言或多或少都提供过的一些方便程序员开发代码的语法,它只是编译器实现的一些小把戏罢了,编译期间以特定的字节码或者特定的方式对这些语法做一些处理,开发者就可以直接方便地使用了。这些语法糖虽然不会提供实质性的功能改进,但是它们或能提高性能、或能提升语法的严谨性、或能减少编码出错的机会。Java提供给了用户大量的语法糖,比如泛型、自动装箱、自动拆箱、foreach循环、变长参数、内部类、枚举类、断言(assert)等。


可变长度参数


先讲可变长度参数,看一段代码:


public static void main(String[] args)

{

    print("000", "111", "222", "333");

}

     

public static void print(String... strs)

{

    for (int i = 0; i

    {

        System.out.println(strs[i]);

    }

}


print方法的参数的意思是表示传入的String个数是不定的,看一下代码的运行结果:


000

111

222

333


我用数组遍历的方式成功地将输入的参数遍历出来了,这说明两个问题:


1、可以使用遍历数组的方式去遍历可变参数


2、可变参数是利用数组实现的


既然这样,那我其实main函数也可以这么写,完全可以:


String[] strs = {"000", "111", "222", "333"};

print(strs);


那直接传入一个数组不就好了?问题是,数组是要指定长度的,万一这次我想传2个String,下次我想传3个String怎么办呢?


最后,注意一点,可变长度参数必须作为方法参数列表中的的最后一个参数且方法参数列表中只能有一个可变长度参数。


foreach循环原理


以前对foreach循环就是这么用着,触动我去研究foreach循环的原理的原因是大概两个月前,自己写了一个ArrayList,想用foreach循环遍历一下看一下写的效果,结果报了空指针异常。本文就写写foreach循环的原理,先看一下这么一段代码:


public static void main(String[] args)

{

    List list = new ArrayList();

    list.add("111");

    list.add("222");

     

    for (String str : list)

    {

        System.out.println(str);

    }

}


用foreach循环去遍历这个list,结果就不说了,都知道。看一下Java是如何处理这个foreach循环的,javap反编译一下:


F:\代码\MyEclipse\TestArticle\bin\com\xrq\test21>javap -verbose TestMain.class


反编译出来的内容很多,有类信息、符号引用、字节码信息,截取一段信息:


public static void main(java.lang.String[]);

    flags: ACC_PUBLIC, ACC_STATIC

    Code:

      stack=2, locals=4, args_size=1

         0: new           #16                 // class java/util/ArrayList

         3: dup

         4: invokespecial #18                 // Method java/util/ArrayList."

it>":()V

         7: astore_1

         8: aload_1

         9: ldc           #19                 // String 111

        11: invokeinterface #21,  2           // InterfaceMethod java/util/List.

add:(Ljava/lang/Object;)Z

        16: pop

        17: aload_1

        18: ldc           #27                 // String 222

        20: invokeinterface #21,  2           // InterfaceMethod java/util/List.

add:(Ljava/lang/Object;)Z

        25: pop

        26: aload_1

        27: invokeinterface #29,  1           // InterfaceMethod java/util/List.

iterator:()Ljava/util/Iterator;


看不懂没关系,new、dup、invokespecial这些本来就是字节码指令表内定义的指令,虚拟机会根据这些指令去执行指定的C++代码,完成每个指令的功能。关键看到21、22这两行就可以了,看到了一个iterator,所以得出结论:在编译的时候编译器会自动将对for这个关键字的使用转化为对目标的迭代器的使用,这就是foreach循环的原理。进而,我们再得出两个结论:


1、ArrayList之所以能使用foreach循环遍历,是因为ArrayList所有的List都是Collection的子接口,而Collection是Iterable的子接口,ArrayList的父类AbstractList正确地实现了Iterable接口的iterator方法。之前我自己写的ArrayList用foreach循环直接报空指针异常是因为我自己写的ArrayList并没有实现Iterable接口


2、任何一个集合,无论是JDK提供的还是自己写的,只要想使用foreach循环遍历,就必须正确地实现Iterable接口


实际上,这种做法就是23中设计模式中的迭代器模式。


数组呢?


上面的讲完了,好理解,但是不知道大家有没有疑问,至少我是有一个疑问的:数组并没有实现Iterable接口啊,为什么数组也可以用foreach循环遍历呢?先给一段代码,再反编译:


public static void main(String[] args)

{

    int[] ints = {1,2,3,4,5};

         

    for (int i : ints)

        System.out.println(i);

}


同样反编译一下,看一下关键的信息:


0: iconst_2

         1: newarray       int

         3: dup

         4: iconst_0

         5: iconst_1

         6: iastore

         7: dup

         8: iconst_1

         9: iconst_2

        10: iastore

        11: astore_1

        12: aload_1

        13: dup

        14: astore        5

        16: arraylength

        17: istore        4

        19: iconst_0

        20: istore_3

        21: goto          39

        24: aload         5

        26: iload_3

        27: iaload

        28: istore_2

        29: getstatic     #16                 // Field java/lang/System.out:Ljav

a/io/PrintStream;

        32: iload_2

        33: invokevirtual #22                 // Method java/io/PrintStream.prin

tln:(I)V

        36: iinc          3, 1

        39: iload_3

        40: iload         4

        42: if_icmplt     24

        45: return


这是完整的这段main函数对应的45个字节码指令,因为这涉及一些压栈、出栈、推送等一些计算机原理性的内容且对于这些字节码指令的知识的理解需要一些C++的知识,所以就不解释了。简单对照字节码指令表之后,我个人对于这45个字节码的理解是Java将对于数组的foreach循环转换为对于这个数组每一个的循环引用。


看完本文有收获?请转发分享给更多人

关注「ImportNew」,提升Java技能