一、java中的語法糖原理
語法糖(Syntactic sugar),也譯爲糖衣語法,是由英國計算機科學家彼得·蘭丁發明的一個術語,指計算機語言中添加的某種語法,這種語法對語言的功能沒有影響,但是更方便程序員使用。語法糖讓程序更加簡潔,有更高的可讀性。
Java中的泛型,變長參數,自動拆箱/裝箱,條件編譯等都是
二、解語法糖
java中的語法糖只存在於編譯期, 在編譯器將 .java 源文件編譯成 .class 字節碼時, 會進行解語法糖操作, 還原最原始的基礎語法結構。這些語法糖包含條件編譯、斷言、Switch語句與枚舉及字符串結合、可變參數、自動裝箱/拆箱、枚舉、內部類、泛型擦除、增強for循環、lambda表達式、try-with-resources語句、JDK10的局部變量類型推斷等等。
三、語法糖實例
1)switch支持String和枚舉
/**
* 枚舉與Switch語句
* option: --decodeenumswitch false
*/
public int switchEnumTest(EnumTest e) {
switch (e) {
case FOO:
return 1;
case BAP:
return 2;
}
return 0;
}
/**
* 枚舉, JDK1.5開始支持
* option: --sugarenums false
*/
public enum EnumTest {
FOO,
BAR,
BAP
}
switch支持枚舉是通過調用枚舉類默認繼承的父類Enum中的ordinal()方法來實現的, 這個方法會返回枚舉常量的序數。
/**
* 字符串與Switch語句
* option: --decodestringswitch false
*/
public int switchStringTest(String s) {
switch (s) {
default:
System.out.println("Test");
break;
case "BB": // BB and Aa have the same hashcode.
return 12;
case "Aa":
case "FRED":
return 13;
}
System.out.println("Here");
return 0;
}
switch支持字符串是通過hashCode()和equals()方法來實現的, 先通過hashCode()返回的哈希值進行switch, 然後通過equals()方法比較進行安全檢查, 調用equals()是爲了防止可能發生的哈希碰撞。
另外switch還支持byte、short、int、char這幾種基本數據類型, 其中支持char類型是通過比較它們的ascii碼(ascii碼是整型)來實現的。所以switch其實只支持一種數據類型, 也就是整型, 其他諸如String、枚舉類型都是轉換成整型之後再使用switch的。
2)泛型
/**
* 泛型擦除
* option:
*/
public void genericEraseTest() {
List<String> list = new ArrayList<String>();
}
在JVM中沒有泛型這一概念, 只有普通方法和普通類, 所有泛型類的泛型參數都會在編譯時期被擦除, 所以泛型類並沒有自己獨有的Class類對象比如List<Integer>.class, 而只有List.class對象。
3)自動裝箱與拆箱
/**
* 自動裝箱/拆箱
* option: --sugarboxing false
*/
public Double autoBoxingTest(Integer i, Double d) {
return d + i;
}
首先我們知道, 基本類型與包裝類型在某些操作符的作用下, 包裝類型調用valueOf()
方法的過程叫做裝箱, 調用xxxValue()方法
的過程叫做拆箱。所以上面的結果很容易看出, 先對兩個包裝類進行拆箱, 再對運算結果進行裝箱。
4) 方法變長參數
/**
* 可變參數
* option: --arrayiter false
*/
public void varargsTest(String ... arr) {
for (String s : arr) {
System.out.println(s);
}
}
可變參數其實就是一個不定長度的數組, 數組長度隨傳入方法的對應參數個數來決定。可變參數只能在參數列表的末位使用。
5) 枚舉
/**
* 枚舉, JDK1.5開始支持
* option: --sugarenums false
*/
public enum EnumTest {
FOO,
BAR,
BAP
}
當我們自定義一個枚舉類型時, 編譯器會自動創建一個被final修飾的枚舉類來繼承Enum, 所以自定義枚舉類型是無法繼承和被繼承的。當枚舉類初始化時, 枚舉字段引用該枚舉類的一個靜態常量對象, 並且所有的枚舉字段都用常量數組$VALUES來存儲。values()方法內則調用Object的clone()方法, 參照$VALUES數組對象複製一個新的數組, 新數組會有所有的枚舉字段。
6) 內部類
import java.util.*;
import java.io.*;
public class CFRDecompilerDemo {
int x = 3;
/**
* 內部類
* option: --removeinnerclasssynthetics false
*/
public void innerClassTest() {
new InnerClass().getSum(6);
}
public class InnerClass {
public int getSum(int y) {
x += y;
return x;
}
}
}
首先我們要明確, 上述innerClassTest()方法中的this是外部類當前對象的引用, 而InnerClass類中的this則是內部類當前對象的引用。編譯過程中, 編譯器會自動在內部類定義一個外部類的常量引用this$0, 並且在內部類的構造器中初始化this$0, 當外部類訪問內部類時, 會把當前外部類的對象引用this傳給內部類的構造器用於初始化, 這樣內部類就能通過所持有的外部類的對象引用, 來訪問外部類的所有公有及私有成員。
7)條件編譯
/**
* 條件編譯
* option: 不需要參數
*/
public void ifCompilerTest() {
if(false) {
System.out.println("false if");
}else {
System.out.println("true else");
}
}
很明顯, javac編譯器在編譯時期的解語法糖階段, 會將條件分支不成立的代碼進行消除。
8) 斷言
/**
* 斷言, JDK1.4開始支持
* option: --sugarasserts false
*/
public void assertTest(String s) {
assert (!s.equals("Fred"));
System.out.println(s);
}
如上, 當斷言結果爲true時, 程序繼續正常執行, 當斷言結果爲false時, 則拋出AssertionError異常來打斷程序的執行。
9)數值字面量
publicclassTest{
publicstaticvoidmain(String... args) {
int i = 10_000;
System.out.println(i);
}
//反編譯後:
publicclassTest
publicstaticvoidmain(String[] args) {
inti = 10000;
System.out.println(i);
}
}
10)for... each
/**
* 增強for循環
* option: --collectioniter false
*/
public void forLoopTest() {
String[] qingshanli = {"haha", "qingshan", "helloworld", "ceshi"};
List<String> list = Arrays.asList(qingshanli);
for (Object s : list) {
System.out.println(s);
}
}
很明顯, 增強for循環的底層其實還是通過迭代器來實現的, 這也就解釋了爲什麼增強for循環中不能進行增刪改操作。
11) try-with-resources語句
/**
* try-with-resources語句
* option: --tryresources false
*/
public void tryWithResourcesTest() throws IOException {
try (final StringWriter writer = new StringWriter();
final StringWriter writer2 = new StringWriter()) {
writer.write("This is qingshanli1");
writer2.write("this is qingshanli2");
}
}
在JDK7之前, 如IO流、數據庫連接等資源用完後, 都是通過finally代碼塊來釋放資源。而try-with-resources語法糖則幫我們省去了釋放資源這一操作, 編譯器在解語法糖階段時會將它還原成原始的語法結構。
12)lambda表達式
/**
* lambda表達式
* option: --decodelambdas false
*/
public void lambdaTest() {
String[] qingshanli = {"haha", "qingshan", "helloworld", "ceshi"};
List<String> list = Arrays.asList(qingshanli);
// 使用lambda表達式以及函數操作
list.forEach((str) -> System.out.print(str + "; "));
// 在JDK8中使用雙冒號操作符
list.forEach(System.out::println);
}
這裏筆者經驗尚淺, 關於lambda表達式的實現原理暫不做闡述, 以免誤人子弟, 歡迎有興趣的讀者在留言區一起討論。
在此感謝該博主的微博:https://www.cnblogs.com/qingshanli/p/9375040.html#_label4,大部分內容轉自他