S++

千线一眼

导航

JVM-类加载(3)

什么是语法糖

所谓的语法糖,其实就是指java编译器把.java 源码编译为.class 字节码的过程中,
自动生成和转换的一些代码,主要是为了减轻程序员的负担。
(给糖吃不捣蛋)


语法糖1 - 默认构造器

当我们编写一个没有构造方法的类,在编译为class后的代码是有无参构造的。
这个无参构造是编译器帮助我们加上的。


语法糖2 - 自动拆装箱

指的是java中基本类型和包装类型之间的转换。
这个特性是在JDK5开始加入的。
如下代码:

public class Candy2 {
  public static void main(String[] args) {
    Integer x = 1;
    int y = x;
  }
}

在编译阶段会转换为:

public class Candy2 {
  public static void main(String[] args) {
    Integer x = Integer.valueOf(1);
    int y = x.intValue();
  }
}

语法糖3 - 泛型擦除

泛型也是在 JDK 5开始加入的特性,但java 在编译泛型代码后会执行 泛型擦除 的动作,
即泛型信息在编译为字节码之后就丢失了,实际的类型都当做了 Object 类型来处理
如下代码:

public class Candy3 {
  public static void main(String[] args) {
    List<Integer> list = new ArrayList<Integer>;
    list.add(1); // 实际调用的是 List.add(Object e)
    int x = list.get(0); // 实际调用的是 Object obj = List.get(int index)
  }
}

所以在取值时,编译器生成真正的字节码中,还要额外进行类型转换的操作:

int x = ((Integer)list.get(0)).intValue(); // 将Object转换为Integer,并进行拆箱操作

擦除的是字节码上的泛型信息,但 LocalVariableTypeTable 仍然保留了方法参数泛型的信息。


语法糖4 - 可变参数

有这样一个包含可变参数方法的代码

public class Candy4 {
  public static void fun(String... args){
    String[] array = args;
    System.out.println(array);
  } 
  public static void main(String[] args) {
    fun("Hello","World")
  }
}

可变参数 String... args实际是一个String[] args,从代码的赋值语句中可以看出,
同样的java编译器会在编译期间将上述代码转换为:

public class Candy4 {
  public static void fun(String[] args){
    String[] array = args;
    System.out.println(array);
  } 
  public static void main(String[] args) {
    fun("Hello","World")
  }
}

语法糖5 - foreach循环

JDK5之后我们可以使用foreach的循环数组和集合
这里有一个有foreach的代码:

public class Candy5 {
  public static void main(String[] args) {
    int[] array = {1, 2, 3};
    for(int num : array) {
      System.out.println(num);
    }
  }
}

会转换为:

public class Candy5 {
  public Candy5(){
  }
  public static void main(String[] args) {
    int[] array = new int[]{1, 2, 3};
    for(int i = 0; i < array.length; i++) {
      int num = array[i];
      System.out.println(num);
    }
  }
}

语法糖6 - switch

从JDK7开始,switch可以作用于字符串和枚举类,
switch配合String和枚举使用时,变量不能为null,
对于匹配字符串:
在编译时会分为两个switch,并创建一个临时byte变量x,值为-1
第一个switch中字符串会转换为相应的hashCode,然后匹配这些hashCode
根据匹配的情况内部再equals验证是否是要匹配的字符串(防止hashCode冲突),最后根据结果更改x的值
第二个switch会匹配x的值,根据x的值执行相应操作

对于匹配枚举类:
会先定义一个合成类(仅jvm使用,对于程序员不可见)用来映射枚举的ordinal与数组元素的关系
枚举的ordinal表示枚举对象的序号,从0开始
根据数组的值来匹配相应的操作


语法糖7 - 枚举

编译期间对于枚举也进行了优化,使得我们使用少量的代码就能使用枚举
关于Enum类:

  • Enum类有两个成员变量:name和ordinal。其中,name用于记录枚举常量的名字。
  • Enum类有一个构造函数,它有两个入参,分别为name和ordianl赋值。
  • Enum类重写了toString()方法,返回枚举常量的name值。
  • Enum类重写了equals()方法,直接用等号比较。
  • Enum类不允许克隆,clone()方法直接抛出异常。(保证枚举永远是单例的)
  • Enum类实现了Comparable接口,直接比较枚举常量的ordinal的值。
  • Enum类有一个静态的valueOf()方法,可以根据枚举类型以及name返回对应的枚举常量。
  • Enum类不允许反射,为了保证枚举永远是单例的。

语法糖8 - try-with-resources

JDK7新增处理资源关闭的特殊语法
其中资源对象必须实现AutoCloseAble接口
其中设计了一个addSuppressed(Throwable e)方法用于添加被压制异常,防止异常信息丢失


语法糖9 - 重写桥接

方法重写时返回值类型分两种情况:1-父子类返回值完全一致;2-子类返回值可以说父类返回值的子类
对于第二中情况有如下代码:

class A {
  public Number f() {
    return 1;
  }
}

class B extends A {
  @Override
  public Integer f() {
    return 2;
  }
}

对于子类,java编译器会如下转换:

class B extends A {
  public Integer f() {
    return 2;
  }
  // 这个方法才是真正的重写了父类的方法
  public synthetic bridge Number f() {
    return f();
  }
}

桥接方法比较特殊,仅java虚拟机可见,并不会命名冲突


语法糖10 - 内部类

内部类在编译期间会额外生成一个类来使用


对于匿名内部类,如果有引用外部的局部变量,那么这个变量必须是final的
因为,引用的外部局部变量将作为参数传入额外生成的类的构造方法来使用
如果这个外部局部变量变化了,那么类中的属性是没有机会改变的


posted on 2022-05-23 17:09  S++  阅读(18)  评论(0编辑  收藏  举报