引言:語法糖經常是大廠面試官常問的一個知識點,關於 Java 的語法糖很多人可能只是知道其中的某幾個,但卻對整體的結構不了解,本文將詳細介紹 Java 語法糖的知識。
題目
什麽是 Java 語法糖?
推薦解析
什麽是語法糖?
語法糖是指一種程式語言的語法結構,它並不提供新的功能,但使程式碼更易讀、更易寫,提高了開發效率。這些語法糖通常是一種編譯器提供的便利性,它將一種常見的模式或程式碼結構用更簡潔、更易懂的語法表示出來。
語法糖的目的是什麽?
語法糖的目的是提高程式碼的可讀性和編寫效率,而不是引入新的功能或改變語言的本質。開發者可以使用語法糖來編寫更簡潔、更清晰的程式碼,而不必深入了解底層的實作細節。
Java 的語法糖
Switch 支持 String 與列舉
舉個例子
public classswitchDemoString{
publicstaticvoidmain(String[] args){
String str = "nihao";
switch (str) {
case"nihao":
System.out.println("nihao");
break;
case"hello":
System.out.println("hello");
break;
default:
break;
}
}
}
Switch (字串) 是用字串的 hashcode 進行比較,case 中再利用 equals 進行判斷,因為可能會產生 hash 沖突的情況。
泛型
對於 Java 虛擬機器來說,需要在編譯階段透過型別擦除的方式進行解語法糖。
型別擦除的主要過程如下:1.將所有的泛型參數用其最左邊界(最頂級的父型別)型別替換。2.移除所有的型別參數。
舉個例子
Map<String, String> map = new HashMap<String, String>();
map.put("xiaobaitiao", "nihao");
解語法糖之後
Map map = new HashMap();
map.put("xiaobaitiao", "nihao");
在虛擬機器中,泛型的實作涉及到型別擦除(Type Erasure)的概念。型別擦除是指在編譯時泛型型別資訊被擦除,而在執行時,泛型的例項僅保留原始型別(raw type)的資訊。這導致在虛擬機器中,所有泛型類的型別參數在編譯時都會被擦除,並且泛型類並沒有自己獨有的
class
類物件。
具體來說,泛型類在編譯後的字節碼中不會保留其泛型資訊,而是被替換為原始型別。例如,對於
List<String>
或
List<Integer>
,在執行時只存在一個
List
類的物件,而無法透過
List<String>. class
或
List<Integer>. class
這樣的語法來獲取類物件。
這樣的設計是為了保持 Java 的回溯相容性,因為泛型是在 Java 5引入的,而在引入泛型之前的程式碼中是沒有泛型資訊的。因此,為了確保與舊程式碼的互操作性,Java 編譯器使用了型別擦除來處理泛型。
自動裝箱和拆箱
這個語法糖應該是較為熟知的,自動裝箱就是 Java 自動將原始型別值轉換成對應的物件,比如將 int 的變量轉換成 Integer 物件,這個過程叫做裝箱,反之將 Integer 物件轉換成 int 型別值,這個過程叫做拆箱。因為不用人工去轉化,因此是自動裝箱和拆箱。
舉個例子(自動裝箱)
publicstaticvoidmain(String[] args){
int num = 1;
Integer n = num;
}
反編譯
publicstaticvoidmain(String args[])
{
int num = 1;
Integer n = Integer.valueOf(num);
}
自動拆箱
publicstaticvoidmain(String[] args){
Integer num = 10;
int n = num;
}
反編譯
publicstaticvoidmain(String args[])
{
Integer num = Integer.valueOf(10);
int n = num.intValue();
}
總結:裝箱用 valueOf() 方法,拆箱用 xxxValue 方法,比如 intValue。
可變長參數
publicstaticvoidprint(String... strs)
{
for (int i = 0; i < strs.length; i++)
{
System.out.println(strs[i]);
}
}
可以用... 去代表可變長參數,相當於一個一維字串陣列。一般不太常用。
斷言
在 Java 中,
assert
關鍵字是從 JAVA SE 1.4 引入的,為了避免和老版本的 Java 程式碼中使用了
assert
關鍵字導致錯誤,Java 在執行的時候預設是不啟動斷言檢查的。
舉個例子
public classTest{
publicstaticvoidmain(String args[]){
int a = 1;
int b = 1;
assert a == b;
System.out.println("Hello Word");
}
}
當 a不等於b 時,會丟擲斷言的異常,相等才會輸出 HelloWorld,反編譯後相當於一個 if else。
數值字面量
在 java 7 中,數值字面量,不管是整數還是浮點數,都允許在數位之間插入任意多個底線。
public classTest{
publicstaticvoidmain(String... args){
int i = 10_000;
System.out.println(i);
}
}
反編譯後會自動把底線去掉,就是為了方便閱讀。
For-Each
For-Each 是程式設計師都會經常用到的,底層也是普遍都講過是利用 for 迴圈和叠代器,因此使用要註意 fail-fast 機制,建議了解 Stream 流的知識。
舉個例子
publicstaticvoidmain(String... args){
String[] strs = {"Hello"};
for (String s : strs) {
System.out.println(s);
}
List<String> strList = ImmutableList.of("Hello");
for (String s : strList) {
System.out.println(s);
}
}
publicstatictransientvoidmain(String args[])
{
String strs[] = {
"Hello"
};
String args1[] = strs;
int i = args1.length;
for(int j = 0; j < i; j++)
{
String s = args1[j];
System.out.println(s);
}
List strList = ImmutableList.of("Hello");
String s;
for(Iterator iterator = strList.iterator(); iterator.hasNext(); System.out.println(s))
s = (String)iterator.next();
}
註意事項
1)方法多載,但由於泛型擦除,導致編譯失敗
publicstaticvoidmethod(List<String> list){
System.out.println("Hello World String list");
}
publicstaticvoidmethod(List<Long> list){
System.out.println("Hello World Long list");
}
2)try catch 中的異常不能再去區別異常的泛型型別,同樣是泛型擦除機制。
3)自動拆箱和裝箱,但是整數值有緩存機制。
publicstaticvoidmain(String[] args){
Integer a = 129;
Integer b = 129;
Integer c = 100;
Integer d = 100;
System.out.println(a == b);
System.out.println(c == d);
}
輸出結果
false
true
-128 到 127 和緩存機制可以實作重用,也就是說使用的是相同的物件參照,這個也比較經典。
4)增強 for 迴圈,上文有提到過,增強 for 底層有用到叠代器,叠代器在遍歷的時候物件不允許修改或者刪除。因此 CMS 異常也就是 fail-fast 多執行緒修改,解決方法可以直接用叠代器或者 Stream 流的方法,推薦 Stream 流掌握。
總結
語法糖在大多數情況下被我們所使用,能夠較大地提升開發效率,但是在開發效率提升的同時,我們要明白語法糖的底層原理,也就是反編譯後的程式碼,JVM 虛擬機器是怎麽最佳化的,有哪些最佳化機制,可能會發生什麽問題,這是我們需要註意的點,避免踩坑。
其他補充
魚聰明 AI 的回答:
魚聰明 AI 地址:https://www.yucongming.com/
Java 的語法糖是指一些在語法層面上提供的便利性,使得程式碼更加簡潔和易讀。這些語法糖並不引入新的功能,而是提供了一種更方便的語法形式。以下是 Java 中常見的語法糖:
泛型: Java 中的泛型提供了參數化型別的能力,使得在編寫集合類等泛型類時可以更安全和更易用。但實際上,Java 的泛型是透過型別擦除來實作的,所以在執行時泛型資訊是不可見的。
自動裝箱和拆箱: 基本數據型別和對應的包裝型別之間可以自動進行轉換,這使得程式碼更易讀。例如,
int
到Integer
的轉換。增強的 for 迴圈: 提供了更簡潔的方式來叠代集合或陣列,避免了手動管理索引的麻煩。
列舉型別: 提供了更簡潔、型別安全的列舉定義方式,避免了使用常量的寫死。
可變參數(Varargs): 允許方法接受可變數量的參數,而無需手動建立陣列。
Lambda 運算式: 引入了函數語言程式設計的概念,使得可以更輕松地編寫簡潔的匿名函式。
Diamond 語法(菱形語法): 在建立泛型例項時,允許省略型別參數的重復聲明。
使用語法糖可能遇到的坑:
泛型型別擦除: 雖然泛型提供了更安全的集合操作,但由於型別擦除的存在,可能會導致在執行時無法獲取泛型型別資訊。
自動裝箱拆箱帶來的效能開銷: 自動裝箱和拆箱可能導致額外的效能開銷,特別是在大量數據操作時。
Lambda 運算式的閉包問題: 在使用 Lambda 運算式時,需要註意對外部變量的存取,因為 Lambda 運算式預設是對外部變量的「讀取」操作,對於局部變量需要是最終的(effectively final)。
總結:
Java 的語法糖提供了一系列便捷性,使得程式碼更加簡潔、易讀,並提高了開發效率。然而,在使用語法糖時需要註意可能遇到的一些陷阱,如泛型型別擦除、自動裝箱拆箱的效能開銷,以及 Lambda 運算式的閉包問題。理解這些細節有助於更好地利用語法糖,同時避免潛在的問題。
推薦文章
文章:https://zhuanlan.zhihu.com/p/600475561
歡迎交流
在閱讀完本文之後,你應該了解什麽是語法糖,語法的目的,在 Java 中常見語法糖的套用,以及需要註意的事項,如何利用好語法糖加快開發效率並且避免踩坑是一個重點,接下來我將提出關於語法糖的三個問題,小夥伴可以評論區留言踴躍發表見解,一起交流進步!
1)如何在執行時獲取泛型類的資訊進行處理?
2)如何有效避免或降低自動裝箱和拆箱帶來的效能問題?
3)如何避免或解決 Lambda 運算式的閉包問題?
往期推薦