语法糖 java
接下来几篇文章要开启一个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 < strs.length; i++) { System.out.println(strs[i]); } }
print方法的参数的意思是表示传入的String个数是不定的,看一下代码的运行结果:函数
000 111 222 333
我用数组遍历的方式成功地将输入的参数遍历出来了,这说明两个问题:性能
一、可使用遍历数组的方式去遍历可变参数编码
二、可变参数是利用数组实现的spa
既然这样,那我其实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<String> list = new ArrayList<String>(); 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
反编译出来的内容不少,有类信息、符号引用、字节码信息,截取一段信息:
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++代码,完成每一个指令的功能。关键看到2一、22这两行就能够了,看到了一个iterator,因此得出结论:在编译的时候编译器会自动将对for这个关键字的使用转化为对目标的迭代器的使用,这就是foreach循环的原理。进而,咱们再得出两个结论:
一、ArrayList之因此能使用foreach循环遍历,是由于 ArrayList全部的List都是Collection的子接口,而Collection是Iterable的子接口,ArrayList的父类 AbstractList正确地实现了Iterable接口的iterator方法。以前我本身写的ArrayList用foreach循环直接报空指针 异常是由于我本身写的ArrayList并无实现Iterable接口
二、任何一个集合,不管是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); }
一样反编译一下,看一下关键的信息:
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循环转换为对于这个数组每个的循环引用。