技术控

    今日:59| 主题:49369
收藏本版 (1)
最新软件应用技术尽在掌握

[其他] Java语法糖(1):可变长度参数以及foreach循环原理

[复制链接]
纸飞机的春天 发表于 2016-11-28 07:17:17
36 0

立即注册CoLaBug.com会员,免费获得投稿人的专业资料,享用更多功能,玩转个人品牌!

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
语法糖

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

  先讲可变长度参数,看一段代码:
  1. public static void main(String[] args)
  2. {
  3.     print("000", "111", "222", "333");
  4. }

  5. public static void print(String... strs)
  6. {
  7.     for (int i = 0; i < strs.length; i++)
  8.     {
  9.         System.out.println(strs[i]);
  10.     }
  11. }
复制代码
print方法的参数的意思是表示传入的String个数是不定的,看一下代码的运行结果:  
  我用数组遍历的方式成功地将输入的参数遍历出来了,这说明两个问题:
  1、可以使用遍历数组的方式去遍历可变参数
  2、可变参数是利用数组实现的
  既然这样,那我其实main函数也可以这么写,完全可以:
  1. String[] strs = {"000", "111", "222", "333"};
  2. print(strs);
复制代码
那直接传入一个数组不就好了?问题是,数组是要指定长度的,万一这次我想传2个String,下次我想传3个String怎么办呢?
  最后,注意一点,    可变长度参数必须作为方法参数列表中的的最后一个参数且方法参数列表中只能有一个可变长度参数。  
  foreach循环原理

  以前对foreach循环就是这么用着,触动我去研究foreach循环的原理的原因是大概两个月前,自己写了一个ArrayList,想用foreach循环遍历一下看一下写的效果,结果报了空指针异常。本文就写写foreach循环的原理,先看一下这么一段代码:
  1. public static void main(String[] args)
  2. {
  3.     List<String> list = new ArrayList<String>();
  4.     list.add("111");
  5.     list.add("222");

  6.     for (String str : list)
  7.     {
  8.         System.out.println(str);
  9.     }
  10. }
复制代码
用foreach循环去遍历这个list,结果就不说了,都知道。看一下Java是如何处理这个foreach循环的,javap反编译一下:
  1. F:\代码\MyEclipse\TestArticle\bin\com\xrq\test21>javap -verbose TestMain.class
复制代码
反编译出来的内容很多,有类信息、符号引用、字节码信息,截取一段信息:
  1. public static void main(java.lang.String[]);
  2.     flags: ACC_PUBLIC, ACC_STATIC
  3.     Code:
  4.       stack=2, locals=4, args_size=1
  5.          0: new           #16                 // class java/util/ArrayList
  6.          3: dup
  7.          4: invokespecial #18                 // Method java/util/ArrayList."<in
  8. it>":()V
  9.          7: astore_1
  10.          8: aload_1
  11.          9: ldc           #19                 // String 111
  12.         11: invokeinterface #21,  2           // InterfaceMethod java/util/List.
  13. add:(Ljava/lang/Object;)Z
  14.         16: pop
  15.         17: aload_1
  16.         18: ldc           #27                 // String 222
  17.         20: invokeinterface #21,  2           // InterfaceMethod java/util/List.
  18. add:(Ljava/lang/Object;)Z
  19.         25: pop
  20.         26: aload_1
  21.         27: invokeinterface #29,  1           // InterfaceMethod java/util/List.
  22. 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循环遍历呢?先给一段代码,再反编译:
  1. public static void main(String[] args)
  2. {
  3.     int[] ints = {1,2,3,4,5};

  4.     for (int i : ints)
  5.         System.out.println(i);
  6. }
复制代码
同样反编译一下,看一下关键的信息:
  1. 0: iconst_2
  2.          1: newarray       int
  3.          3: dup
  4.          4: iconst_0
  5.          5: iconst_1
  6.          6: iastore
  7.          7: dup
  8.          8: iconst_1
  9.          9: iconst_2
  10.         10: iastore
  11.         11: astore_1
  12.         12: aload_1
  13.         13: dup
  14.         14: astore        5
  15.         16: arraylength
  16.         17: istore        4
  17.         19: iconst_0
  18.         20: istore_3
  19.         21: goto          39
  20.         24: aload         5
  21.         26: iload_3
  22.         27: iaload
  23.         28: istore_2
  24.         29: getstatic     #16                 // Field java/lang/System.out:Ljav
  25. a/io/PrintStream;
  26.         32: iload_2
  27.         33: invokevirtual #22                 // Method java/io/PrintStream.prin
  28. tln:(I)V
  29.         36: iinc          3, 1
  30.         39: iload_3
  31.         40: iload         4
  32.         42: if_icmplt     24
  33.         45: return
复制代码
这是完整的这段main函数对应的45个字节码指令,因为这涉及一些压栈、出栈、推送等一些计算机原理性的内容且对于这些字节码指令的知识的理解需要一些C++的知识,所以就不解释了。简单对照字节码指令表之后,我个人对于这45个字节码的理解是    Java将对于数组的foreach循环转换为对于这个数组每一个的循环引用。
友荐云推荐




上一篇:Lambdoku – AWS Lambda with Heroku-like Experience
下一篇:ThreadLocal内存泄露
酷辣虫提示酷辣虫禁止发表任何与中华人民共和国法律有抵触的内容!所有内容由用户发布,并不代表酷辣虫的观点,酷辣虫无法对用户发布内容真实性提供任何的保证,请自行验证并承担风险与后果。如您有版权、违规等问题,请通过"联系我们"或"违规举报"告知我们处理。

*滑动验证:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

我要投稿

推荐阅读

扫码访问 @iTTTTT瑞翔 的微博
回页顶回复上一篇下一篇回列表手机版
手机版/CoLaBug.com ( 粤ICP备05003221号 | 文网文[2010]257号 )|网站地图 酷辣虫

© 2001-2016 Comsenz Inc. Design: Dean. DiscuzFans.

返回顶部 返回列表