语法糖(Syntactic Sugar),也称糖衣语法,是由英国计算机科学家彼得·约翰·兰达发明的一个术语,指在计算机语言中添加的某种语法,这种语法对语言的功能并无影响
,可是更方便程序员使用。
java
几乎各类语言或多或少都提供过一些语法糖来方便程序员的代码开发,这些语法糖虽然不会提供实质性的功能改进
,可是它们或能提升效率,或能提高语法的严谨性,或能减小编码出错的机会。不过也有一种观点认为语法糖并不必定都是有益的,大量添加和使用“含糖”的语法,容易让程序员产生依赖,没法看清语法糖的糖衣背后,程序代码的真实面目。
程序员
Java在现代编程语言之中属于“低糖语言”(相对于C#及许多其余JVM语言来讲),尤为是JDK 1.5以前的版本。Java中最经常使用的语法糖主要是前面提到过的泛型(泛型并不必定都是语法糖实现,如C#的泛型就是直接由CLR支持的)、变长参数、自动装箱/拆箱,遍历循环(Foreach循环)等,虚拟机运行时不支持这些语法,它们在编译阶段还原回简单的基础语法结构,这个过程称为解语法糖。
编程
泛型是JDK 1.5的一项新增特性,它的本质是参数化类型(Parametersized Type)的应用,也就是说所操做的数据类型被指定为一个参数。这种参数类型能够用在类、接口和方法的建立中,分别称为泛型类、泛型接口和泛型方法。数组
泛型思想早在C++语言的模板(Template)中就开始生根发芽,在Java语言处于尚未 出现泛型的版本时,只能经过Object是全部类型的父类和类型强制转换两个特色的配合来实现类型泛化。bash
JDK 1.5以前使用HashMap的get()方法,返回值 就是一个Object对象,因为Java语言里面全部的类型都继承于java.lang.Object,因此Object转 型成任何对象都是有可能的
。可是也由于有无限的可能性,就只有程序员和运行期的虚拟机才知道这个Object究竟是个什么类型的对象。在编译期间,编译器没法检查这个Object的强制转型是否成功
,若是仅仅依赖程序员去保障这项操做的正确性,许多ClassCastException
的风险就会转嫁到程序运行期之中。编程语言
C#里面 泛型不管在程序源码中、编译后的IL中,或是运行期的CLR中,都是切实存在的,List<int>与List<String>就是两个不一样的类型
,它们在系统运行期生成,有本身的虚方法表和类型数据,这种实现称为类型膨胀,基于这种方法实现的泛型称为真实泛型。ui
Java语言中的泛型则不同,它只在程序源码中存在
,在编译后的字节码文件中,就已经替换为原来的原生类型(Raw Type,也称为裸类型)了,而且在相应的地方插入了强制转型代码
,所以,对于运行期的Java语言来讲,ArrayList<int>与ArrayList<String>就是同一 个类
,因此泛型技术其实是Java语言的一颗语法糖,Java语言中的泛型实现方法称为类型擦除
,基于这种方法实现的泛型称为伪泛型
。编码
当泛型碰见重载spa
public class GenericTypes{
public static void method(List<String> list){
System.out.println("invoke method(List<String>)");
}
public static void method(List<Integer> list){
System.out.println("invoke method(List<Integer>)");
}
}复制代码
这段代码是不能被编译的,由于参数List<Integer>和List<String>编译以后都被擦除了,变成了同样的原生类型List<E>
,擦除动做致使这两种方法的特征签名变得如出一辙。
code
// 编译前
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 2, 3, 4);
// 若是在JDK1.7中,还有另外的一刻语法糖
// 能让上面这句代码进一步简写成List<Integer> list = [1,2,3,4];
int sum = 0;
for (int i : list) {
sum += i;
}
System.out.println(sum);
}复制代码
代码清单中一共包含了泛型、自动装箱、自动拆箱、遍历循环与变长参数5种语法糖。
// 编译后
public static void main(String[] args) {
List list = Arrays.asList(
new Integer[]{Integer.valueOf(1), Integer.valueOf(2), Integer.valueOf(3), Integer.valueOf(4)});
int sum = 0;
for (Iterator localIterator = list.iterator(); localIterator.hasNext(); ) {
int i = ((Integer) localIterator.next()).intValue();
sum += i;
}
System.out.println(sum);
}复制代码
代码清单则展现了它们在编译后的变化。
泛型就没必要说了,自动装箱、拆箱在编译以后被转化成了对应的包装和还原方法,如本例中的Integer.valueOf()与Integer.intValue()方法,而遍历循环则把代码还原成了迭代器的实现,这也是为什么遍历循环须要被遍历的类实现Iterable接口的缘由。最后再看看变长参数,它在调用的时候变成了一个数组类型的参数,在变长参数出现以前,程序员就是使用数组来完成相似功能的。
public static void main(String[] args) {
Integer a = 1;
Integer b = 2;
Integer c = 3;
int d = 3;
Integer e = 127;
Integer f = 127;
Integer e1 = 321;
Integer f1 =321;
Long g = 3L;
System.out.println(c.equals(d)); // true
System.out.println(c == d); // true
System.out.println(c == (a + b)); // true
System.out.println(e == f); // true
System.out.println(e1 == f1); // false
System.out.println(e1.equals(f1)); // true
System.out.println(c.equals(a + b)); // true
System.out.println(g == (a + b)); // true
System.out.println(g.equals(a + b)); // false
}复制代码
包装类的“==”运算在不遇到算术运算的状况下不会自动拆箱,以及它们equals()方法不处理数据转型的关系。
JVM会自动维护八种基本类型的常量池,int常量池中初始化-128~127的范围,因此当为Integer i=127时,在自动装箱过程当中是取自常量池中的数值,而当Integer i=128时,128不在常量池范围内,因此在自动装箱过程当中需new 128,因此地址不同。对于Integer来讲,你用==比较的是对象引用地址,而不是Integer的值。Integer你要把当当成一个对象来看待