詳解Java中常見語法糖的使用
簡介
語法糖(Syntactic Sugar),也稱糖衣語法,是由英國計算機學家 Peter.J.Landin 發(fā)明的一個術語,指在計算機語言中添加的某種語法,這種語法對語言的功能并沒有影響,但是更方便程序員使用。簡而言之,語法糖讓程序更加簡潔,非常利于操作。事實上聽名字也能想到,加在語法中的糖讓語法變得更”甜“。
糖1:switch
switch對于char, byte, short, int類型是本身就支持的,但其實它們都是轉換成了整型,最后支持的其實是整型。但由于Java語法糖的出現(xiàn),switch也支持String和enum類型了
原代碼
public class SugarTest {
public static void main(String[] args) {
String str = "java";
switch (str) {
case "java":
System.out.println("1");
break;
case "javac":
System.out.println("2");
break;
default:
System.out.println("default");
}
}
}
編譯后代碼
public class SugarTest {
public SugarTest() {
}
public static void main(String[] args) {
String str = "java";
byte var3 = -1;
switch(str.hashCode()) {
case 3254818:
if (str.equals("java")) {
var3 = 0;
}
break;
case 100899457:
if (str.equals("javac")) {
var3 = 1;
}
}
switch(var3) {
case 0:
System.out.println("1");
break;
case 1:
System.out.println("2");
break;
default:
System.out.println("default");
}
}
}
可以看出,switch對于字符串類是比較字符串的hashcode方法以及通過equals方法確定是哪個字符串的,由于hashcode有一定概率會出現(xiàn)hash碰撞,用hashcode比較可能不太安全,所以仍需要進一步通過equals比較
糖2:自動拆裝箱
自動拆裝箱實際上是通過包裝類的valueOf方法和xxxValue方法實現(xiàn)的,具體見我的一篇文章解鎖Java自動拆裝箱的神秘面紗專門介紹
糖3:for-each語法
增強for循環(huán)可謂是廣泛使用,省去了去定義一個自增變量
public class SugarTest {
public static void main(String[] args) {
String[] strs = new String[]{"java", "python", "c++"};
for (String str : strs) {
System.out.println(str);
}
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 3; i++) {
list.add(i);
}
for (Integer num : list) {
System.out.println(num);
}
}
}
編譯后代碼
public class SugarTest {
public SugarTest() {
}
public static void main(String[] args) {
String[] strs = new String[]{"java", "python", "c++"};
String[] var2 = strs;
int i = strs.length;
for(int var4 = 0; var4 < i; ++var4) {
String str = var2[var4];
System.out.println(str);
}
List list = new ArrayList();
for(i = 0; i < 3; ++i) {
list.add(i);
}
Iterator var7 = list.iterator();
while(var7.hasNext()) {
Integer num = (Integer)var7.next();
System.out.println(num);
}
}
}
可以看出,一般的數(shù)組使用for-each背后的實現(xiàn)僅僅是把它變?yōu)槠胀╢ot循環(huán)而已,對于集合類使用for-each循環(huán)背后的實現(xiàn)是使用了Iterator遍歷的
由于使用了迭代器Iterator,所以在數(shù)組遍歷時不能修改數(shù)組(比如調用remove方法),否則根據(jù)fail-fast機制會拋出java.util.ConcurrentModificationException異常
糖4:方法變長參數(shù)
Java是允許將一種類型的任意數(shù)量的值作為參數(shù)使用的
public class SugarTest {
public static void main(String[] args) {
test("java", "python", "c++");
}
public static void test(String... strs) {
for (String str : strs) {
System.out.println(str);
}
}
}
編譯后的代碼
public class SugarTest {
public SugarTest() {
}
public static void main(String[] args) {
test("java", "python", "c++");
}
public static void test(String... strs) {
String[] var1 = strs;
int var2 = strs.length;
for(int var3 = 0; var3 < var2; ++var3) {
String str = var1[var3];
System.out.println(str);
}
}
}
事實上就是將參數(shù)轉變?yōu)榱藢臄?shù)組
糖5:if條件編譯
條件編譯就是在編譯期間編譯器會選擇有效的代碼編譯,不滿足條件的代碼稱為無用代碼,會被直接丟棄
public class SugarTest {
private static final boolean DEBUG = true;
public static void main(String[] args) {
if (DEBUG) {
System.out.println("DEBUG模式");
} else {
System.out.println("非DEBUG模式");
}
}
}
編譯后的代碼
public class SugarTest {
private static final boolean DEBUG = true;
public SugarTest() {
}
public static void main(String[] args) {
System.out.println("DEBUG模式");
}
}
上面的代碼中編譯器在編譯時已經(jīng)能確定只用執(zhí)行DEBUG為true的代碼,那么編譯器只需要保存滿足條件的一行代碼即可,不需要全部保存
談一談條件編譯的作用
有人會問了,既然DEBUG已知為true了,還去寫個if多此一舉干嘛,這其實是為了后期便于維護,就像上述代碼一樣,DEBUG模式可能是一段代碼,非DEBUG模式可能是另外一段代碼,開發(fā)者只需要改變常量DEBUG的值即可做到兩種模式下的自由切換,豈不樂哉?這樣配合編譯器的條件編譯機制又能使編譯后的代碼更為簡潔,簡直perfect?。ㄍǔ_@個常量可通過配置文件配置更為簡便)
糖6:泛型
泛型可謂是個好東西了,在沒有泛型時我們返回的方法值、參數(shù)等都在編寫代碼時就必須確定它的類型,這為我們帶來了諸多不便,我們往往需要將這些參數(shù)和返回值設置為Object類型然后強轉,但基于開發(fā)者的強轉是極易出現(xiàn)問題的。而有了泛型后這些問題便都不是問題了
public class SugarTest {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("111");
}
}
編譯后的代碼
public class SugarTest {
public static void main(String[] args) {
List list = new ArrayList();
list.add("111");
}
}
可以看出,泛型在編譯期間便直接被扔掉了。泛型的實現(xiàn)機制是通過編譯器的一種被稱為類型擦除(type erasure)的處理實現(xiàn)的,也就是編譯器不認識List這個類,它只認識并執(zhí)行List這個類
類型擦除主要兩個過程
- 將所有使用到泛型參數(shù)的地方都用其最頂級的層次(頂層父類)替換掉
- 將所有泛型擦除,即將尖括號<>刪除
如下這個例子
public class SugarTest {
public static void main(String[] args) {
String s = "1234";
test(s);
}
public static <T extends Object> void test(T arg) {
List<T> list = new ArrayList<>();
list.add(arg);
}
}
編譯后的代碼將所有泛型替換為Object型
public class SugarTest {
public static void main(String[] args) {
String s = "1234";
test((Object)s);
}
public static void test(Object arg) {
List list = new ArrayList();
list.add(arg);
}
}
糖7:內(nèi)部類
class OutClass {
class Node {
public Integer num;
}
}
public class SugarTest {
public static void main(String[] args) {
OutClass outClass = new OutClass();
OutClass.Node node = outClass.new Node();
node.num = 10;
}
}
編譯后事實上會產(chǎn)生兩個后綴為.class的字節(jié)碼文件,OutClass.class和OutClass$Node.class文件,由此可以看出內(nèi)部類并不是真正套在一個類的內(nèi)部,而是分成兩個類編譯
再看看靜態(tài)內(nèi)部類實際上是一樣的
class OutClass {
static class Node {
public Integer num;
}
}
public class SugarTest {
public static void main(String[] args) {
OutClass.Node node = new OutClass.Node();
node.num = 10;
}
}
從上我們也能看出內(nèi)部類和靜態(tài)內(nèi)部類的一些區(qū)別,內(nèi)部類必須通過它的外部類的實例去new一個內(nèi)部類,而靜態(tài)內(nèi)部類可以直接new一個內(nèi)部類(但是要求內(nèi)部類存在時外部類必定也存在)
糖8:assert斷言
assert關鍵字是后來引入的,為了避免和老版本的Java代碼引入assert關鍵字而出錯,默認Java是不開啟斷言檢查的,也就是說assert等于無用,如果要開啟斷言檢查使用選項-ea或-enableassertions開啟
【即java -ea SugarTest運行】
public class SugarTest {
public static void main(String[] args) {
Integer num = 1;
assert num != null : "Error!";
System.out.println(num);
}
}
編譯后的代碼
public class SugarTest {
public SugarTest() {
}
public static void main(String[] args) {
Integer num = 1;
if(!$assertionsDisabled && num == null) {
throw new AssertionError("Error!");
} else {
System.out.println(num);
return;
}
}
static final boolean $assertionsDisabled = !src/SugarTest.desiredAssertionStatus();
}
斷言首先會檢查$assertionsDisabled是否為true,如果為true說明斷言被禁用了,直接執(zhí)行else的內(nèi)容,如果設置了開啟斷言則會判斷斷言后的表達式是否正確,不正確會拋出異常
糖9:數(shù)值字面量
允許在數(shù)字間插入任意數(shù)量的下劃線,對數(shù)字的值不會產(chǎn)生任何影響,主要是方便閱讀
【比如1_0000_0000可以直接看出是1億】
public class SugarTest {
public static void main(String[] args) {
Integer num = 1_0000_0000;
System.out.println(num);
}
}
編譯后的代碼
public class SugarTest {
public SugarTest() {
}
public static void main(String[] args) {
Integer num = 100000000;
System.out.println(num);
}
}
糖10:Enum枚舉類
枚舉類讓我們可以更方便的使用和定義一些常量,但在代碼中實際上enum就是個關鍵字,看不出來枚舉類到底是個什么神仙玩意,也看不出它是個什么對象
public enum EnumTest {
JAVA, PYTHON, JS;
}
編譯后的代碼
public final class EnumTest extends Enum {
public static final EnumTest JAVA;
public static final EnumTest PYTHON;
public static final EnumTest JS;
private static final EnumTest $VALUES[];
static {
JAVA = new EnumTest("JAVA", 0);
PYTHON = new EnumTest("PYTHON", 1);
JS = new EnumTest("JS", 2);
$VALUES = (new EnumTest[]{
JAVA, PYTHON, JS
});
}
private EnumTest(String s, int i) {
super(s, i);
}
public static EnumTest[] values() {
return (EnumTest[]) $VALUES.clone();
}
public static EnumTest valueOf(String name) {
return (EnumTest) Enum.valueOf(EnumTest, name);
}
}
可以看出,每一個枚舉類中的一個常量都是一個EnumTest對象,這個對象有兩個屬性,一個是常量的名字,另一個是從0開始的數(shù)值字面量
所有變量定義為final連類也定義為final,說明枚舉類的變量不能被修改該類也不能被繼承,values方法返回的是數(shù)組的深拷貝,所以即使通過該方法獲得了數(shù)組也無法修改數(shù)組中的值
枚舉類的構造器是私有化的,所以用戶定義的每一個變量返回給用戶時都是同一個EnumTest對象,如果只在枚舉類定義一個常量,那么這個枚舉類就是個單例模式了
糖11:try-with-resource異常處理
相信大家都寫過try-catch-finally代碼,每次new一個流時一般都要在finally中將這個流close,如果流比較多,代碼就會顯得臃腫,那么這個語法糖就能讓代碼及其簡潔
public class SugarTest {
public static void main(String[] args) {
try (BufferedReader reader = new BufferedReader(
new FileReader("c:\\file\\test.txt"));
BufferedWriter writer = new BufferedWriter(new PrintWriter(System.out))) {
String str;
while ((str = reader.readLine()) != null) {
System.out.println(str);
}
writer.write("讀取文件完畢");
} catch (IOException e) {
e.printStackTrace();
}
}
}
編譯后代碼
public class SugarTest {
public SugarTest() {
}
public static void main(String[] args) {
try {
BufferedReader reader = new BufferedReader(new FileReader("c:\\file\\test.txt"));
Throwable var2 = null;
try {
BufferedWriter writer = new BufferedWriter(new PrintWriter(System.out));
Throwable var4 = null;
try {
String str;
while((str = reader.readLine()) != null) {
System.out.println(str);
}
writer.write("讀取文件完畢");
} catch (Throwable var29) {
var4 = var29;
throw var29;
} finally {
if (writer != null) {
if (var4 != null) {
try {
writer.close();
} catch (Throwable var28) {
var4.addSuppressed(var28);
}
} else {
writer.close();
}
}
}
} catch (Throwable var31) {
var2 = var31;
throw var31;
} finally {
if (reader != null) {
if (var2 != null) {
try {
reader.close();
} catch (Throwable var27) {
var2.addSuppressed(var27);
}
} else {
reader.close();
}
}
}
} catch (IOException var33) {
var33.printStackTrace();
}
}
}
事實上這么簡潔的代碼最后編譯器也只是把它轉換成了try-catch-finally而已,代碼故意采取兩個流,目的是讓讀者清楚編譯器會先對writer釋放流再對reader釋放流,也就是先寫的流后釋放
糖12:Lambda表達式
public class SugarTest {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("JAVA");
list.forEach((elem) -> System.out.println(elem));
}
}
編譯后的代碼
public class Lambda {
public static void main(String[] arrstring) {
List list = new ArrayList();
list.add("JAVA");
list.forEach((Consumer<String>)LambdaMetafactory.metafactory(null, null, null, (Ljava/lang/Object;)V, lambda$main$0(java.lang.String ), (Ljava/lang/String;)V)());
}
private static void lambda$main$0(String s) {
System.out.println(s);
}
}
可以看出編譯后端Lambda語法是新增了一個方法,方法中是匿名內(nèi)部類的方法體,然后調用LambdaMetafactory.metafactory這個方法執(zhí)行
以上就是詳解Java中常見語法糖的使用的詳細內(nèi)容,更多關于Java語法糖的資料請關注腳本之家其它相關文章!
相關文章
SpringBoot配置動態(tài)數(shù)據(jù)源的實戰(zhàn)詳解
Spring對數(shù)據(jù)源的管理類似于策略模式,不懂策略模式也沒關系,其實就是有一個全局的鍵值對,類型是Map<String, DataSource>,當JDBC操作數(shù)據(jù)庫之時,會根據(jù)不同的key值選擇不同的數(shù)據(jù)源,本文介紹了SpringBoot配置動態(tài)數(shù)據(jù)源的方法,需要的朋友可以參考下2024-08-08
Spring Boot打開URL出現(xiàn)signin問題的解決
這篇文章主要介紹了Spring Boot打開URL出現(xiàn)signin問題的解決,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-12-12
SpringCloud之@FeignClient()注解的使用詳解
@FeignClient是SpringCloud中用于聲明一個Feign客戶端的注解,用于解決模塊方法互相調用的問題,Feign是一個聲明式的WebService客戶端,通過Feign,只需要創(chuàng)建一個接口,并使用注解來描述請求,就可以直接執(zhí)行HTTP請求了2024-11-11

