语法糖的存在主要是方便开发人员使用,使程序更加简洁,提高程序可读性。Java虚拟机并不支持这些语法糖,这些语法糖在编译阶段就会被还原成简单的基础语法结构,这个过程就是解语法糖。


1. Java7新增switch对String的支持

String str = "awecoder";
switch (str) {
    case "awecoder":
        System.out.println(str);
        break;
    default:
        System.out.println("other");
}

Jad反编译后

String str = "awecoder";
String s = str;
byte byte0 = -1;
switch(s.hashCode())
{
case -1520954730: 
    if(s.equals("awecoder"))
        byte0 = 0;
    break;
}
switch(byte0)
{
case 0: // '\0'
    System.out.println(str);
    break;

default:
    System.out.println("other");
    break;
}

从代码中可以看出,switch(String)是通过hashCode()和equals()方法组合实现的。对字符串对象的哈希值做switch操作,并在内部使用equals方法做安全校验,避免哈希碰撞。hashCode方法返回的是int类型。由于用的是较新的jdk版本(1.8.0_221),反编译结果与很多博客有所不同,主要体现在第二次对byte的switch操作。

switch在JDK1.5新增对包装类型支持,JVM在编译时会进行拆箱操作,而枚举类是采用枚举类的ordinal方法,返回int类型。综上,swtich仅支持基本数据类型。

【问题】在同时运行swtich(String)和swtich(Integer)两个小案例时,jad反编译代码有如下几句。

String str = "awecoder";
Integer integer = str;  // 把str引用直接赋值给了integer引用
switch(integer.hashCode()) {...}

2. 泛型

Java编译器在处理泛型时采用代码共享的方式,为每个泛型类型创建唯一的字节码表示。通过类型擦除的方式实现泛型类型到唯一字节码的映射。

类型擦除的操作过程是,将所有泛型参数用其最左边界类型替换,并移除所有类型参数。例如List<Map<String, Object>> 泛型类型,在反编译后的代码是List

Class c1 = new ArrayList<String>().getClass();
Class c2 = new ArrayList<Integer>().getClass();
System.out.println(c1 == c2); // true

在泛型代码的内部是无法获取到任何有关泛型类型参数信息的。在案例中,c1和c2得到的字节码是完全相同的,通过反编译我们可以get其中的原因,c1c2被赋值的均为((new ArrayList()).getClass()

3. 自动拆装箱

Java不是完全的面向对象语言,存在8种基本数据类型,它们不是对象,不需要通过new创建变量,变量直接存储值到栈中。为了更好的使用这些数据类型,Java分别提供了包装类。拆装箱就是实现基本数据类型与对象之间的转换。

Integer i = 42;
反编译后为
Integer i = Integer.valueOf(42);

4. 方法变长参数

对于调用方法的实参,会根据可变长参数的实际参数创建给定类型的数组,再把数组作为真正的实参传递给被调方法。

method(42, "awe","coder");
public static void method(int i, String...strs) {}

反编译后代码
method(42, new String[] {
    "awe", "coder"
});
public static transient void method(int j, String as[]){}

其他语法糖:for-each/枚举/内部类/编译时优化/try-with-resource

枚举:对于枚举enum,其不是新的类型,而是关键字。对于枚举类我们反编译可以得知,枚举类其实是创建了一个继承Enum类的不可变类。

public static final class t extends Enum

内部类:是编译时的概念,编译成功后会生成外部类和内部类多个字节码文件。例如,例如上面的内部枚举类,在编译后会得到Test.class和Test$t.class文件。

编译时优化:JVM在编译时会优化可以优化的部分,例如常量。下面的例子中,如果i没有final修饰,只是普通变量,JVM便不会进行该优化。

final int i = 1;
int j = i + 1;
反编译后得到
int i = 1;
int j = 2;

另一个编译时优化的例子是条件编译。例子中的条件语句是不会被JVM编译的,同样此处也要求常量。

final boolean b = false;
if (b) {
    System.out.println("Hello, b!");
}
反编译后为
boolean b = false;

for-each:其内部仍然是采用普通for循环和迭代器实现遍历。

try-with-resource是JDK1.7中新增的功能,用于代码更加优雅的关闭资源。

参考资料

  1. Hollis知识星球博文
  2. 《Java编程思想》
Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐