语法糖,又称语法糖衣,是英国计算机科学家发明的一个术语,指在计算机语言中加入某种语法,这种语法对语言的功能并没有影响,但是方便了程序员的操作,并且增加了代码的可读性,减少了出错的机会。
Java在现代编程语言中,属于“低糖语言”(相对于JVM和c#)来说,尤其在JDK1.5之前,语法糖很少出现。Java中最常用的语法糖主要是泛型、变长参数、自动装箱、拆箱等,虚拟机运行时不支持这些语法糖操作,他们在编译后将还原为简单的语法结构,这个过程称为解语法糖。
泛型技术在Java和C#中看来很相似,但是他们有本质的区别。C#中,泛型无论在源代码、编译后的IL或者是在CLR中,都是切实存在的,List<int>和List<String>就是两个不同的类型,他们在系统运行期生成,有自己的虚方法表和类型数据,这种实现称为类型膨胀,基于这种语法实现的泛型称为真实泛型。
Java语言中则不一样,在编译后的字节码中,就已经替换为原生类型了,并且在相应的地方加入了强制转换代码, 因此对于运行期的Java语言来说,ArrayList<Integer>和ArrayList<String>是一样的,所以泛型是Java的语法糖。
来看下面一个使用泛型的代码
public class Sugar { public static void main(String[] args) { Map<String,Integer> map=new HashMap<String, Integer>(); map.put("miss", 0); map.put("you", 2); System.out.println(map.get("miss")); } }
对它的字节码进行反编译
public class Sugar { public static void main(String[] paramArrayOfString) { HashMap localHashMap = new HashMap(); localHashMap.put("miss", Integer.valueOf(0)); localHashMap.put("you", Integer.valueOf(2)); System.out.println(localHashMap.get("miss")); } }
反编译之后发现发现发型都不见了,程序变成了Java泛型出现之前的写法,
当我们定义以下两个两个方法的时候,按照我们的知识基础以及Java语法糖的知识,它是不能被编译通过的,因此这两个方法的方法签名是一样的
public class ReloadTest2 { public void method1(List<String> arg1) { } public void method1(List<Integer> arg2) { } }
但再来看这样一个例子,再使用SDK的javac命令时是可以编译通过的,而且也能够运行。
public String method1(List<String> arg1) { return null; } public int method1(List<Integer> arg2) { return 0; }
对它的字节码进行反编译,似乎又看到了泛型。
public class ReloadTest1 { public String method1(List<String> paramList) { return null; } public int method1(List<Integer> paramList) { return 0; } }
那这是什么原因呢?我们知道返回值是不参与重载的,但是如果两个方法有相同的方法签名,但是返回值不同的话,它们是可以共存在一个Class文件中的。其实这样是毫无意义的,这只是一种“妥协”的方式,对于编程没有任何意义,甚至还带来了混乱。因此对于Eclipse来说,依然拒绝编译上面的代码
直接来看编译前后的代码对比,就可以看到是如何实现语法糖的了。
编译前
public static void main(String[] args) { List<Integer> list=new ArrayList<Integer>(); list.add(500); list.add(2); list.add(new Integer(20)); for(int sub:list) { System.out.println(sub); } }
反编译
public static void main(String[] args) { List list = new ArrayList(); list.add(Integer.valueOf(500)); list.add(Integer.valueOf(2)); list.add(new Integer(20)); for (Iterator localIterator = list.iterator(); localIterator.hasNext(); ) { int sub = ((Integer)localIterator.next()).intValue(); System.out.println(sub); } }
可以看到,自动装箱的实现就是调用了Integer.valueOf方法,而foreach实际上是在调用list的iterator进行遍历。这也就是为了foreach方法要求被遍历的集合必须实现了Iterator接口。
来看看什么叫条件编译。
编译前
public static void main(String[] args) { if(true) { System.out.println("true"); } else { System.out.println("false"); } }
编译后
public class ConditionCompiler { public static void main(String[] args) { System.out.println("true"); } }
这里Java编译器对代码进行了简化,它根据布尔常量的真假,去掉了分支不成的代码块。只有使用条件为常量的If语句才能达到上面的效果,如果使用常量与其他类型的流程控制语句,则可能会编译出错,拒绝编译。