语法糖(Syntactic Sugar),也称糖衣语法,是由英国计算机学家 Peter.J.Landin 发明的一个术语,指在计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。Java 中最常用的语法糖主要有泛型、变长参数、条件编译、自动拆装箱、内部类等。
Java泛型
类的类型的参数化,就是泛型。 或者说: 类的类型也可以作为参数变量传递给类或类的方法。
java1.5后可以用尖括号<>
声明一个泛型。
Oracle官方的Documentation中对泛型(Generic Types)的解释:
A generic type is a generic class or interface that is parameterized over types.
官方给出了以下例程:
/**
* Generic version of the Box class.
* @param <T> the type of the value being boxed
*/
public class Box<T> {
// T stands for "Type"
private T t;
public void set(T t) { this.t = t; }
public T get() { return t; }
}
为了便于阅读,在Java中有人使用 T E K V 等字母。这些对编译器来说都是一样的,可以是任意字母。
T : Type (类型) E : Element(元素) K : Key(键) V : Value(值)
泛型的类型参数只能是引用类型,不能是原始类型(int,char,double,long等)。
另外尖括号<>
中可以使用?
通配符,extends
和super
关键字:
// `?`通配符可以用来表示任意类型
List<?> barList;
// 限定上边界
public <T extends Comparable<T>> T maximum(T x, T y, T z) {}
// 限定下边界
public class Student<? super Integer> {}
另外,<>中可以加入多个类型参数(Type Parameters),官方给出了以下例程:
public interface Pair<K, V> {
public K getKey();
public V getValue();
}
public class OrderedPair<K, V> implements Pair<K, V> {
private K key;
private V value;
public OrderedPair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() { return key; }
public V getValue() { return value; }
}
// The following statements create two instantiations of the OrderedPair class:
Pair<String, Integer> p1 = new OrderedPair<String, Integer>("Even", 8);
Pair<String, String> p2 = new OrderedPair<String, String>("hello", "world");
Java中的参数化类型只在源码中存在,在编译后的字节码中,已经被替换为原来的原生类型了,并且在相应的地方插入了强制转换代码。对于运行期的Java 语言来说,Integer型的ArrayList和String型的ArrayList就是同一个类。所以说Java语言中的泛型实现方法称为类型擦除,基于这种方法实现的泛型称为伪泛型。
(new ArrayList<Integer>()).getClass() == (new ArrayList<String>()).getClass()
Java自动装箱和拆箱
在Java SE5之前,如果要生成一个数值为10的Integer对象,必须这样进行
Integer i = new Integer(10);
而在从Java SE5开始就提供了自动装箱的特性,如果要生成一个数值为10的Integer对象,只需要这样就可以了:
Integer i = 10;
这个过程中会自动根据数值创建对应的Integer对象,这就是装箱。跟装箱对应,拆箱就是自动将包装器类型转换为基本数据类型
原始类型{boolean, byte, char, float, int, long, short, double}
包装类型{Boolean, Byte, Character, Float, iInterger, long, Short, Double}
实际上是由编译器对这个过程自动进行了翻译:
// 装箱源代码
int i = 10;
Integer n = i;
// 反编译后
int i = 10;
Integer n = Integer.valueOf(i);
// 拆箱源代码
Integer i = 10;
int n = i;
// 反编译后
Integer i = Integer.valueOf(10);
int n = i.intValue();
装箱过程是通过调用包装器的valueOf
方法实现的,而拆箱过程是通过调用包装器的xxxValue
方法实现的。
装拆箱操作会在以下情况时发生:
- 进行 = 赋值操作(装箱或拆箱)
- 进行+,-,*,/混合运算 (拆箱)
- 进行>,<,==比较运算(拆箱)
- 调用equals进行比较(装箱)
- ArrayList,HashMap等集合类 添加基础类型数据时(装箱)
然后就引出了一道非常有意思的Java面试题:
public void testAutoBox2() {
//1
int a = 100;
Integer b = 100;
System.out.println(a == b);
//2
Integer c = 100;
Integer d = 100;
System.out.println(c == d);
//3
c = 200;
d = 200;
System.out.println(c == d);
}
第一个因为基础类和包装类进行比较,包装类会被拆箱成基础类,基础类int的比较是值的比较,因此是true
包装类的比较是引用的比较,因此例如
new Integer(1) == new Integer(1)
的结果是false
第二个和第三个的结果很有意思,第二个的结果是true,而第三个的结果是false。 原因和Integer.valueOf()的实现有关:
MAC版IDEA使用
Command+左键点击方法名
可以跳转到源码
public final class Integer extends Number implements Comparable<Integer> {
// ...
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
// ...
}
可以看出,包装器Integer初始化了一个叫做IntegerCache的静态数组,这样当需要产生一个在[-128, 127]之间的Integer对象时,无需调用Integer的构造函数,只需从IntegerCache[]中取出对应元素就可以了,这样节省了空间和调用构造函数的时间,只有在需要产生一个不在[-128, 127]区间的Integer才会单独生成一个Integer对象。 因此题目中值为100的Integer都是IntegerCache[]中的同一个元素,因此是相同的引用;而值为100的Integer是新生成的对象,是不同的引用。
Java可变长参数
在 Java 5 中提供了变长参数,允许在调用方法时传入不定长度的参数。变长参数是Java的一个语法糖,本质上还是基于数组的实现。
在定义方法时,在最后一个形参后加上三个点...
,就表示该形参可以接受多个参数值,多个参数值被当成数组传入。
- 可变参数只能作为函数的最后一个参数,但其前面可以有也可以没有任何其他参数
- 由于可变参数必须是最后一个参数,所以一个函数最多只能有一个可变参数
- Java的可变参数,会被编译器转型为一个数组
public static void test(String... args) { for(String arg : args) { System.out.println(arg); }