1. 程式人生 > >早期(編譯期)優化——語法糖的味道

早期(編譯期)優化——語法糖的味道

文章目錄


一、泛型與型別擦除

1.1 泛型與型別擦除

Java中的泛型與C#中的泛型區別

Java中的泛型是偽泛型,而C#中的泛型是真泛型。Java中的泛型型別在編譯成位元組碼後,是不區分型別的,都被替換為原生型別了(泛型擦除),並且在相應地方插入了強制型別轉換。而C#中的泛型在編譯後是真正事實存在的(泛型膨脹)。

Java中泛型例子

public static void main
(String argc[]) { Map<String, String> map = new HashMap<String, String>(); map.put("hello", "你好"); map.put("how are you", "吃了沒?"); System.out.println(map.get("hello")); System.out.println(map.get("how are you")); }

使用反編譯工具後

 public static void main
(String[] var0) { HashMap var1 = new HashMap();//泛型不見了,被替換為原生型別了 var1.put("hello", "你好"); var1.put("how are you", "吃了沒?"); System.out.println((String)var1.get("hello"));//這兒的強制型別轉換 System.out.println((String)var1.get("how are you"));//這兒的強制型別轉換 }

JDK設計團隊為什麼要用型別擦除實現Java泛型呢?

可能是實現簡單、相容性吧!!!

Java泛型喪失的優雅

import java.util.ArrayList;

public class Test {
    public static String method(ArrayList<String> list) {
        System.out.println("invoke method(ArrayList<String> list)");
        return "";
    }

    public static int method(ArrayList<Integer> list) {
        System.out.println("invoke method(ArrayList<Integer> list)");
        return 1;
    }
    
    public static void main(String argc[]) {
        method(new ArrayList<String>());
        method(new ArrayList<Integer>());
    }
}

它竟然可以執行,這不是它違法了過載規範。而是在位元組碼層面特徵標籤包括方法返回型別和受查異常表。並且,在Java層面,ArrayList與ArrayList是兩個不同的型別(如果是一樣型別的話,那就會報’method(ArrayList)’ is already defined in ‘Test’)。不過這樣的話會報’method(ArrayList)’ clashes with ‘method(ArrayList)’; both methods have same erasure錯誤(有相同的泛型擦除),此時我們使得返回型別不同就行了。

二、自動裝箱、拆箱與遍歷迴圈

例子:

import java.util.Arrays;
import java.util.List;

public class Test {
    public static void main(String argc[]) {
        List<Integer> list= Arrays.asList(1,2,3,4);//語法糖:變長引數、自動裝箱、泛型
        int sum=0;
        for(int i:list){//語法糖:自動拆箱、迴圈遍歷
            sum+=i;
        }
        System.out.println(sum);
    }
}

用jd-gui反編譯的結果:

import java.io.PrintStream;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

public class Test
{
  public static void main(String[] argc)
  {
    List list = Arrays.asList(new Integer[] { Integer.valueOf(1), Integer.valueOf(2), Integer.valueOf(3), Integer.valueOf(4) });
    int sum = 0;
    for (Iterator i$ = list.iterator(); i$.hasNext(); ) { int i = ((Integer)i$.next()).intValue();
      sum += i;
    }
    System.out.println(sum);
  }
}

當然,語法糖雖甜,但是會長蛀牙哦!!!
例子:

public class Test {
    public static void main(String argc[]) {
       Integer a=1;
       Integer b=2;
       Integer c=3;
       Integer d=3;
       Integer e=321;
       Integer f=321;
       Long g=3L;
       System.out.println(c==d);//1.true
        System.out.println(e==f);//2.false
        System.out.println(c==(a+b));//3.true
        System.out.println(c.equals(a+b));//4.true
        System.out.println(g==(a+b));//5.ture
        System.out.println(g.equals(a+b));//6.false,因為a+b後結果為int,在找過載函式時,自動封裝為Integer去了
    }
}

至於1~3和5為什麼這樣,參考Java自動封裝和自動拆箱。至於4和6,要知道一點——過載方法的匹配順序:
byte–>short–>int–>long–>float–>double–>對應的裝箱類–>(現在可以上轉型了)。所以a+b後被封裝為Integer了。現在又要看equals原始碼了:

 public boolean equals(Object obj) {
	if (obj instanceof Long) {//很顯然Integer不是Long
	    return value == ((Long)obj).longValue();
	}
	return false;
    }

由上面程式碼就知道了,4為ture,6為false.

當然,順手貼上一下反編譯結果:

import java.io.PrintStream;

public class Test
{
  public static void main(String[] argc)
  {
    Integer a = Integer.valueOf(1);
    Integer b = Integer.valueOf(2);
    Integer c = Integer.valueOf(3);
    Integer d = Integer.valueOf(3);
    Integer e = Integer.valueOf(321);
    Integer f = Integer.valueOf(321);
    Long g = Long.valueOf(3L);
    System.out.println(c == d);
    System.out.println(e == f);
    System.out.println(c.intValue() == a.intValue() + b.intValue());
    System.out.println(c.equals(Integer.valueOf(a.intValue() + b.intValue())));
    System.out.println(g.longValue() == a.intValue() + b.intValue());
    System.out.println(g.equals(Integer.valueOf(a.intValue() + b.intValue())));
  }
}

三、條件編譯

C++中提供了預編譯(如#include)。java語言並沒有使用前處理器(因為java編譯器並非一個個地編譯Java檔案,而是將所有編譯單元的語法樹頂級節點輸入到待處理列表後在進行編譯,因此各個檔案之間能夠互相提供符號資訊)。但是,java語言也是可以實現條件編譯的——使用條件為常量的if語句。
如:

public class Test {
    public static void main(String argc[]) {
      if(true){
          System.out.println("執行true分支");
      }else{
          System.out.println("執行false分支");
      }
    }
}

反編譯之後的結果:

import java.io.PrintStream;

public class Test
{
  public static void main(String[] argc)
  {
    System.out.println("執行true分支");
  }
}

需要注意的是只能使用條件為常量的if語句才能達到上述效果,如果使用常量與其他帶有條件判斷能力的語句搭配,則可能拒絕編譯。