上文讲到在语义分析中会对Java中的语法糖进行解糖操做,所以本文就主要讲述一下Java中有哪些语法糖,每一个语法糖在解糖事后的原始代码,以及这些语法糖背后的逻辑。java
语法糖(Syntactic sugar),也译为糖衣语法,是由英国计算机科学家彼得·约翰·兰达(Peter J. Landin)发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能并无影响,可是更方便程序员使用。一般来讲使用语法糖可以增长程序的可读性,从而减小程序代码出错的机会。 -----百度百科程序员
在编程领域中,除了语法糖的概念还有语法盐,语法糖精,语法海洛因这些新奇的概念,感兴趣的读者自行Google一下,本文篇幅有限,就不展开来讲了.web
从百度百科的描述来看,语法糖只是做为一种更快捷的语法,其并不会改变所要实现的功能,所以本文就以Java中的语法糖来验证一下是否如此.编程
源代码以下所示数组
public class TestForEach { public static void main(String[] args) { // 验证 循环迭代 ArrayList<Integer> test = new ArrayList<Integer>(); test.add(1); for (Integer integer : test) { System.out.println(integer); } Integer[] array = new Integer[test.size()]; array = test.toArray(array); for (Integer integer : array) { System.out.println(integer); } } }
用jad反编译后以下所示jvm
public class TestForEach { public TestForEach() { } public static void main(String args[]) { ArrayList arraylist = new ArrayList(); arraylist.add(Integer.valueOf(1)); Integer integer; for(Iterator iterator = arraylist.iterator(); iterator.hasNext(); System.out.println(integer)) integer = (Integer)iterator.next(); Integer ainteger[] = new Integer[arraylist.size()]; ainteger = (Integer[])arraylist.toArray(ainteger); Integer ainteger1[] = ainteger; int i = ainteger1.length; for(int j = 0; j < i; j++) { Integer integer1 = ainteger1[j]; System.out.println(integer1); } } }
根据上面反编译后的代码能够看出集合元素的循环迭代底层是经过迭代器来实现的.而数组的循环则是经过原始的for循环来实现的.函数
经过上面的代码咱们还能够看出泛型这个概念在javac编译时是不存在的,编译器会将全部的泛型替换掉,在使用时,直接采用类型转换的方式来获得结果.也正是泛型的这个特征可能出现下面这个问题.工具
public void test1(ArrayList<Integer> a){} public void test1(ArrayList<String> a){}
以上代码是没法编译经过的,由于根据上文获得的结论,泛型在编译时,会消除全部的泛型的限定,那么上面两个方法的签名都会一致,不知足函数重载的条件.优化
Java支持自动拆装箱,即将基本类型和其包装类型之间进行自动替换,那这种方式又是如何实现的呢.this
原始代码以下所示:
// 自动拆装箱 Integer a = 1; int b = new Integer(a);
通过反编译后,代码以下所示
Integer integer = Integer.valueOf(1); int i = (new Integer(integer.intValue())).intValue();
能够看到Java实现基本类型 -- >包装类型,是经过XXX.valueOf()
来实现的,而包装类型 --> 基本类型是经过xxxValue()
来实现的.
咱们都知道switch--case只对int和char类型的数据有效,但从java7开始switch已经能够支持String类型了,这背后的逻辑又是什么,下面咱们反编译一下代码看看其本质是如何实现的.
String hello = "1"; switch (hello){ case "hello": System.out.println("Hello"); break; case "world": System.out.println("World"); break; default: System.out.println("HelloWorld"); break; }
初始代码如上所示,咱们在switch中比较了字符串,根据字符串的不一样来实现不一样的分支,那这种逻辑是如何实现的呢.
String s = "1"; String s1 = s; byte byte0 = -1; switch(s1.hashCode()) { case 99162322: if(s1.equals("hello")) byte0 = 0; break; case 113318802: if(s1.equals("world")) byte0 = 1; break; } switch(byte0) { case 0: // '\0' System.out.println("Hello"); break; case 1: // '\001' System.out.println("World"); break; default: System.out.println("HelloWorld"); break; } }
能够看到字符串是经过比较字符串的hashcode来进行比较,当两个字符串的hashCode值相同时,再经过equals()来肯定其是否真正相同.
所以 Switch 比较 String 的本质仍是比较 int 类型的数据。
变长参数,即容许在方法调用时传入不定数量的参数.具体使用以下所示:
public static void unSignedArgs(String... a){ for (String s : a) { System.out.println(s); } } public static void main(String[] args) { unSignedArgs("1","3","4"); }
在unSignedArgs()
方法中,咱们定义了一个变长参数,而后在方法调用的时候,传入3个参数.那么这种方法是如何实现的.
public static transient void unSignedArgs(String as[]) { String as1[] = as; int i = as1.length; for(int j = 0; j < i; j++) { String s = as1[j]; System.out.println(s); } } public static void main(String args[]) { unSignedArgs(new String[] { "1", "3", "4" }); } }
能够看到变长参数,本质是经过数组来实现的,首先在方法定义时,将变长参数转换为了数组.而后在方法调用的时候是将传入的参数转换成数组而后再传入定义的方法中。
在过去操做资源时,使用try-catch-finally
语句,须要开发人员手动在finally中关闭资源。但如今官方提倡使用 try-with-resource
来操做资源,那么该语法是如何使用的呢。
源代码以下所示:
try (FileInputStream fileInputStream = new FileInputStream("dfd")){ fileInputStream.read(); }catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }
反编译后代码以下所示:
FileInputStream fileinputstream; Throwable throwable; Exception exception; fileinputstream = new FileInputStream("dfd"); throwable = null; try { fileinputstream.read(); } catch(Throwable throwable2) { throwable = throwable2; throw throwable2; } finally { if(fileinputstream == null) goto _L0; else goto _L0 } if(fileinputstream != null) if(throwable != null) try { fileinputstream.close(); } catch(Throwable throwable1) { throwable.addSuppressed(throwable1); } else fileinputstream.close(); break MISSING_BLOCK_LABEL_104; if(throwable != null) try { fileinputstream.close(); } catch(Throwable throwable3) { throwable.addSuppressed(throwable3); } else fileinputstream.close(); throw exception; Object obj; obj; ((FileNotFoundException) (obj)).printStackTrace(); break MISSING_BLOCK_LABEL_104; obj; ((IOException) (obj)).printStackTrace(); }
能够看到编译器自动帮助咱们进行资源的关闭,减小了编程人员出错的可能.
数值字面量即在多位数值中穿插入_,方便开发人员快速掌握数值的大小.
int a = 10_000; System.out.println(a+1);
那么这个语法糖的含义是什么呢,反编译后以下所示
char c = '\u2710'; System.out.println(c + 1);
从结果能够看到编译器是不会管下划线的,其只会将数值正常的读写出来.
int a = 1; int b = 2; assert a == b; System.out.println(a+b);
翻译后代码以下所示
{ int i = 1; byte byte0 = 2; if(!$assertionsDisabled && i != byte0) { throw new AssertionError(); } else { System.out.println(i + byte0); return; } }
从代码中能够清楚地看到断言的底层实现机制是用if
语句来实现,若是条件不符合,则抛出异常.
Java中的条件编译,是经过永真或永假if
来实现的,编译器会判断条件是否符合,从而来判断是否进行编译.
源代码以下所示:
// 条件编译 if (true){ System.out.println("true"); }else{ System.out.println("false"); }
反编译后代码以下:
{ System.out.println("true"); }
从代码中咱们能够看到,编译器对永远不会执行的代码进行了不编译的处理,从而达到了条件编译的效果.但其实笔者感受条件编译在Java中用处不大,做用就是在不一样的模式或机器下,能够编译执行不一样的代码.不过有总比没有好.
在这里咱们说内部类是一个语法糖,是由于其仅仅是一个编译时的概念,在编译阶段,编译器会将外部类和内部类进行编译,从而生成两个不一样的文件,以下所示:
public class TestForEach { public class Children{ } } [260259@localhost src]$ ll 总用量 16 -rw-rw-r--. 1 260259 260259 331 5月 21 11:04 TestForEach$Children.class -rw-rw-r--. 1 260259 260259 335 5月 21 11:04 TestForEach.class -rw-rw-r--. 1 260259 260259 506 5月 21 11:04 TestForEach.jad -rw-rw-r--. 1 260259 260259 2206 5月 21 11:06 TestForEach.java
反编译后,以下所示:
public class TestForEach { public class Children{ final TestForEach this$0; public Children(){ this$0 = TestForEach.this; super(); } }
枚举是一种特殊的数据接口,其中包含了一种特殊的数据接口,以key-value 的形式来存储数据,那么 enum 是一种类吗,其内部又是如何实现的呢。
首先咱们定义一个枚举:
public enum testEnum { SPRING,SUMMER,AUTUMN,WINTER }
此时,咱们对这个枚举进行反编译,
public final class testEnum extends Enum { public static testEnum[] values() { return (testEnum[])$VALUES.clone(); } public static testEnum valueOf(String s) { return (testEnum)Enum.valueOf(testEnum, s); } private testEnum(String s, int i) { super(s, i); } public static final testEnum SPRING; public static final testEnum SUMMER; public static final testEnum AUTUMN; public static final testEnum WINTER; private static final testEnum $VALUES[]; static { SPRING = new testEnum("SPRING", 0); SUMMER = new testEnum("SUMMER", 1); AUTUMN = new testEnum("AUTUMN", 2); WINTER = new testEnum("WINTER", 3); $VALUES = (new testEnum[] { SPRING, SUMMER, AUTUMN, WINTER }); } }
从结果,咱们能够看出,首先枚举是一个编译时的概念,这也说明了其是Java的一个语法糖,编译器在编译的时候自动生成了一个类继承自Enum
,同时声明为final
,这也为枚举是不可继承的提供了理论基础.
lambda做为Java8新出的一个功能点其实也是一种语法糖.由于笔者这边没有Java8及以上版本的反编译工具,所以这边就不详细描述了,粗略说一下,lambda做为一个语法糖,其内部实际上是经过相关的两个底层Api来实现的.
在本文中,笔者介绍了Java中的12个语法糖,做为开发人员了解这些语法糖的用法以及其内部的含义,可让咱们更加高效地开发业务代码,同时也可让咱们了解编译器的优化逻辑.从而提升程序的编写效率和运行效率.
文章在公众号"iceWang"第一手更新,有兴趣的朋友能够关注公众号,第一时间看到笔者分享的各项知识点,谢谢!笔芯.
本系列文章主要借鉴自 <深刻分析javaweb技术内幕> 和 <深刻理解java虚拟机-jvm高级特性与最佳实践> .