編譯期處理(語法糖)
java編譯器把.java
源碼編譯成.class
字節碼的過程,自動生成和轉換的一些代碼。
默認構造器
public class Candy01 {
}
編譯成class后的代碼
public class Candy1 {public Candy1(){super();}
}
自動拆裝箱(jdk5加入)
public class Candy02 {public static void main(String[] args) {/*** jdk5之前:* Integer x = Integer.valueOf(1);* int y = x.intValue();*/Integer x = 1; // 自動拆箱int y = x; // 自動裝箱}
}
泛型集合取值
public class Candy03 {public static void main(String[] args) {List<Integer> list = new ArrayList<>();list.add(10); // 實際調用:List.add(Object e)Integer x = list.get(0); // 實際調用的是 Object obj = List.get(int idx);}
}
編譯器在獲取真正字節碼時,需要額外做一個類型轉換的操作:
Integer x = (Integer)list.get(0);
可變參數
public class Candy04 {public static void foo(String... args) {String[] array = args;System.out.println(array);}public static void main(String[] args) {foo("hello", "world");}
}
編譯后的代碼:
public class Candy04 {public static void foo(String[] args) {String[] array = args;System.out.println(array);}public static void main(String[] args) {foo(new String[]{"hello", "world"});}
}
如果調用了
foo()
,則等價于foo(new String[]{})
,創建了一個空數組,而不會直接傳null進去
數組 - foreach循環
public class Candy05_01 {public static void main(String[] args) {int[] array = {1, 2, 3, 4, 5};for(int e : array) {System.out.println(e);}}
}
會被編譯為:
int[] array = new int[]{1, 2, 3, 4, 5};
for(int i = 0; i < array.length; i++) {int e = array[i];System.out.println(e);
}
List - foreach循環
public class Candy05_02 {public static void main(String[] args) {List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);for(Integer i : list) {System.out.println(i);}}
}
會被編譯成:
Iterator it = list.iterator();
while(it.hasNext()) {Integer e = (Integer) it.next();System.out.println(e);
}
foreach循環寫法可以配合數組,以及所有實現了Iterator接口的集合類一起使用
switch - 字符串
public class Candy06_01 {public static void choose(String str) {switch(str) {case "hello": {System.out.println("h");break;}case "world": {System.out.println("w");break;}}}
}
會被編譯成:
public class Candy06_01 {public static void choose(String str) {byte x = -1;switch(str.hashCode()) {case 99162322: // "hello"的hash值if (str.equals("hello")) {x = 0;}break;case 113318802: // "world"的hash值if (str.equals("world")) {x = 1;}}switch(x) {case 0:System.out.println("h");break;case 1:System.out.println("w");}}
}
編譯后其實是執行了兩次switch
- 第一次是根據hash和equals將字符串轉換為相應的byte類型
- 第二次才是利用byte進行比較。
第一遍使用hashCode進行比較,是為了提高效率,較少可能的比較;而equals是為了防止hash沖突。
switch - 枚舉類
enum Sex {MALE, FEMALE
}
public class Candy06_02 {public static void foo(Sex sex) {switch(sex) {case MALE: {System.out.println("男");break;}case FEMALE: {System.out.println("女");break;}}}
}
編譯后代碼:
public class Candy06_02 {public static void foo(Sex sex) {// 獲取枚舉的序號(MALE.ordinal() = 0, FEMALE.ordinal() = 1)int ordinal = sex.ordinal(); switch (ordinal) {case 0: // MALESystem.out.println("男");break;case 1: // FEMALESystem.out.println("女");break;}}
}
枚舉類
enum Sex {MALE, FEMALE
}
轉換后代碼:
public final class Sex extends java.lang.Enum<Sex> {// 枚舉常量(public static final)public static final Sex MALE;public static final Sex FEMALE;// 私有構造函數(enum 不能外部實例化)private Sex(String name, int ordinal) {super(name, ordinal);}// 靜態初始化塊(初始化所有枚舉值)static {MALE = new Sex("MALE", 0);FEMALE = new Sex("FEMALE", 1);$VALUES = new Sex[]{MALE, FEMALE};}// 自動生成的方法:values() 返回所有枚舉值public static Sex[] values() {return (Sex[])$VALUES.clone();}// 自動生成的方法:valueOf(String) 根據名字返回枚舉public static Sex valueOf(String name) {return (Sex)Enum.valueOf(Sex.class, name);}// 內部存儲所有枚舉值的數組private static final Sex[] $VALUES;
}
try-with-resources簡化資源關閉
public class Candy07 {public static void main(String[] args) {/*try(資源變量 = 創建資源對象) {}catch() {}*/try(InputStream is = new FileInputStream("d:\\test.txt")) {System.out.println(is);}catch (Exception e) {e.printStackTrace();}}
}
其中:資源對象需要實現
AutoCloseable
接口,例如:InputStream
、OutPutStream
、Connection
、Statement
、ResultSet
等接口都實現了AutoCloseable接口,使用try-with-resources可以不用寫finally語句,編譯器會幫助我們生成資源關閉的代碼。
上邊代碼被編譯為:
public class Candy07 {public static void main(String[] args) {try {InputStream is = new FileInputStream("d:\\test.txt");Throwable t = null;try {System.out.println(is);}catch (Throwable e1){t = e1; // t是我們代碼中出現的異常throw e1;} finally {// 判斷了資源不為空if(is != null) {// 如果我們的代碼有異常if(t != null) {try {is.close();} catch (Throwable e2) {// 如果close出現異常,作為被壓制異常添加t.addSuppressed(e2);}}else {// 如果代碼沒有異常,close出現的異常就是最后catch塊中的eis.close();}}}}catch (IOException e) {e.printStackTrace();}}
}
如果我們的代碼出現了異常,并且關閉資源的時候又出現了異常,如果想讓這兩個異常都顯示,可以使用
壓制異常
。
例如:
public class Candy08 {public static void main(String[] args) {try (MyResource resource = new MyResource()) {int i = 1 / 0; // 除0異常}catch (Exception e) {e.printStackTrace();}}
}
class MyResource implements AutoCloseable {@Overridepublic void close() throws Exception {throw new Exception("close異常"); // 資源關閉異常}
}
輸出:
兩個異常都會被拋出
方法重寫時的橋接方法
class A {public Number m() {return 1;}
}
class B extends A {@Override// 子類m方法的返回值是Integer,是父類m方法返回值Number的子類public Integer m() {return 2;}
}
編譯后的代碼:
class B extends A {public Integer m() {return 2;}// 此方法才是真正重寫了父類public Number m()方法public synthetic bridge Number m() {// 調用public Integer m()return m();}
}
橋接方法比較特殊,只對java虛擬機可見,與原來的public Integer m(),沒有命名沖突。
無參的匿名內部類
public class Candy08 {public static void main(String[] args) {Runnable runnable = new Runnable() {@Overridepublic void run() {System.out.println("ok");}};}
}
編譯后代碼:
// 額外生成的類
final class Candy08$1 implements Runnable {Candy08$1() {}@Overridepublic void run() {System.out.println("ok");}
}
public class Candy08 {public static void main(String[] args) {Runnable runnable = new Candy08$1();}
}
引用局部變量的匿名內部類
public class Candy10 {public static void test(final int x) {Runnable runnable = new Runnable() {public void run() {System.out.println(x);}};}
}
編譯后:
// 額外生成的類
final class Candy10$1 implements Runnable {int val$x;Candy10$1(int x) {this.val$x = x;}public void run() {System.out.println(this.val$x);}}
public class Candy10 {public static void test(final int x) {Runnable runnable = new Candy10$1(x);}
}
這也解釋了為什么匿名內部類里邊使用的變量必須使用final修飾:因為在創建
Candy10$1
對象時,將x的值賦值給了Candy10$1
對象的val$x
屬性,所以x的值不會在發生變化了。如果發生變化,那么val$x
屬性沒有機會再跟著一起變化了。