文法糖(Syntactic Sugar),也稱糖衣文法,指在電腦語言中添加的某種文法,這種文法對語言本身功能來說沒有什麼影響,只是為了方便程式員的開發,提高開發效率。說白了,文法糖就是對現有文法的一個封裝。
Java作為一種與平台無關的進階語言,當然也含有文法糖,這些文法糖並不被虛擬機器所支援,在編譯成位元組碼階段就自動轉換成簡單常用文法。一般來說Java中的文法糖主要有以下幾種:
1. 泛型與類型擦除
2. 自動裝箱與拆箱,變長參數、
3. 增強for迴圈
4. 內部類與枚舉類 泛型與類型擦除
Java語言並不是一開始就支援泛型的。在早期的JDK中,只能通過Object類是所有類型的父類和強制類型轉換來實現泛型的功能。強制類型轉換的缺點就是把編譯期間的問題延遲到運行時,JVM並不能為我們提供編譯期間的檢查。
在JDK1.5中,Java語言引入了泛型機制。但是這種泛型機制是通過類型擦除來實現的,即Java中的泛型只在程式原始碼中有效(原始碼階段提供類型檢查),在編譯後的位元組碼中自動用強制類型轉換進行替代。也就是說,Java語言中的泛型機制其實就是一顆文法糖,相較與C++、C#相比,其泛型實現實在是不那麼優雅。
/*** 在原始碼中存在泛型*/public static void main(String[] args) { Map<String,String> map = new HashMap<String,String>(); map.put("hello","你好"); String hello = map.get("hello"); System.out.println(hello);}
當上述原始碼被編譯為class檔案後,泛型被擦除且引入強制類型轉換
public static void main(String[] args) { HashMap map = new HashMap(); //類型擦除 map.put("hello", "你好"); String hello = (String)map.get("hello");//強制轉換 System.out.println(hello);}
自動裝箱與拆箱
Java中的自動裝箱與拆箱指的是基礎資料型別 (Elementary Data Type)與他們的封裝類型之間的相互轉換。
我們知道Java是一門物件導向的語言,在Java世界中有一句話是這麼說的:“萬物皆對象”。但是Java中的基礎資料型別 (Elementary Data Type)卻不是對象,他們不需要進行new操作,也不能調用任何方法,這在使用的時候有諸多不便。因此Java為這些基本類型提供了封裝類,並且為了使用方便,提供了自動裝箱與拆箱功能。自動裝箱與拆箱在使用的過程中,其實是一個文法糖,內部還是調用了相應的函數進行轉換。
下面代碼示範了自動裝箱和拆箱功能
public static void main(String[] args) { Integer a = 1; int b = 2; int c = a + b; System.out.println(c);}
經過編譯後,代碼如下
public static void main(String[] args) { Integer a = Integer.valueOf(1); // 自動裝箱 byte b = 2; int c = a.intValue() + b;//自動拆箱 System.out.println(c);}
變長參數
所謂變長參數,就是方法可以接受長度不定確定的參數
變長參數特性是在JDK1.5中引入的,使用變長參數有兩個條件,一是變長的那一部分參數具有相同的類型,二是變長參數必須位於方法參數列表的最後面。變長參數同樣是Java中的文法糖,其內部實現是Java數組。
public class Varargs { public static void print(String... args) { for(String str : args){ System.out.println(str); } } public static void main(String[] args) { print("hello", "world"); }}
編譯為class檔案後如下,從中可以很明顯的看出變長參數內部是通過數組實現的
public class Varargs { public Varargs() { } public static void print(String... args) { String[] var1 = args; int var2 = args.length; //增強for迴圈的數組實現方式 for(int var3 = 0; var3 < var2; ++var3) { String str = var1[var3]; System.out.println(str); } } public static void main(String[] args) { //變長參數轉換為數組 print(new String[]{"hello", "world"}); }}
增強for迴圈
增強for迴圈與普通for迴圈相比,功能更強並且代碼更簡潔
增強for迴圈的對象要麼是一個數組,要麼實現了Iterable介面。這個文法糖主要用來對數組或者集合進行遍曆,其在迴圈過程中不能改變集合的大小。
public static void main(String[] args) { String[] params = new String[]{"hello","world"}; //增強for迴圈對象為數組 for(String str : params){ System.out.println(str); } List<String> lists = Arrays.asList("hello","world"); //增強for迴圈對象實現Iterable介面 for(String str : lists){ System.out.println(str); }}
編譯後的class檔案為
public static void main(String[] args) { String[] params = new String[]{"hello", "world"}; String[] lists = params; int var3 = params.length; //數組形式的增強for退化為普通for for(int str = 0; str < var3; ++str) { String str1 = lists[str]; System.out.println(str1); } List var6 = Arrays.asList(new String[]{"hello", "world"}); Iterator var7 = var6.iterator(); //實現Iterable介面的增強for使用iterator介面進行遍曆 while(var7.hasNext()) { String var8 = (String)var7.next(); System.out.println(var8); }}
內部類
內部類就是定義在一個類內部的類
Java語言中之所以引入內部類,是因為有些時候一個類只在另一個類中有用,我們不想讓其在另外一個地方被使用。內部類之所以是文法糖,是因為其只是一個編譯時間的概念,一旦編譯完成,編譯器就會為內部類產生一個單獨的class檔案,名為outer$innter.class。
public class Outer { class Inner{ }}
使用javac編譯後,產生兩個class檔案Outer.class和Outer$Inner.class,其中Outer$Inner.class的內容如下:
class Outer$Inner { Outer$Inner(Outer var1) { this.this$0 = var1; }}
內部類分為四種:成員內部類、局部內部類、匿名內部類、靜態內部類,每一種都有其用法,這裡就不介紹了 枚舉類型
枚舉類型就是一些具有相同特性的類常量
java中類的定義使用class,枚舉類的定義使用enum。在Java的位元組碼結構中,其實並沒有枚舉類型,枚舉類型只是一個文法糖,在編譯完成後被編譯成一個普通的類。這個類繼承java.lang.Enum,並被final關鍵字修飾。
public enum Fruit { APPLE,ORINGE}
使用jad對編譯後的class檔案進行反編譯後得到:
//繼承java.lang.Enum並聲明為finalpublic final class Fruit extends Enum{ public static Fruit[] values() { return (Fruit[])$VALUES.clone(); } public static Fruit valueOf(String s) { return (Fruit)Enum.valueOf(Fruit, s); } private Fruit(String s, int i) { super(s, i); } //枚舉類型常量 public static final Fruit APPLE; public static final Fruit ORANGE; private static final Fruit $VALUES[];//使用數組進行維護 static { APPLE = new Fruit("APPLE", 0); ORANGE = new Fruit("ORANGE", 1); $VALUES = (new Fruit[] { APPLE, ORANGE }); }}