1. 程式人生 > 其它 >JVM-類載入(3)

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的
因為,引用的外部區域性變數將作為引數傳入額外生成的類的構造方法來使用
如果這個外部區域性變數變化了,那麼類中的屬性是沒有機會改變的