新聞中心
這篇文章將為大家詳細(xì)講解有關(guān)Java語(yǔ)法糖的示例分析,小編覺(jué)得挺實(shí)用的,因此分享給大家做個(gè)參考,希望大家閱讀完這篇文章后可以有所收獲。
在額爾古納等地區(qū),都構(gòu)建了全面的區(qū)域性戰(zhàn)略布局,加強(qiáng)發(fā)展的系統(tǒng)性、市場(chǎng)前瞻性、產(chǎn)品創(chuàng)新能力,以專注、極致的服務(wù)理念,為客戶提供成都做網(wǎng)站、網(wǎng)站設(shè)計(jì)、外貿(mào)營(yíng)銷網(wǎng)站建設(shè) 網(wǎng)站設(shè)計(jì)制作按需設(shè)計(jì)網(wǎng)站,公司網(wǎng)站建設(shè),企業(yè)網(wǎng)站建設(shè),品牌網(wǎng)站建設(shè),成都全網(wǎng)營(yíng)銷推廣,外貿(mào)網(wǎng)站制作,額爾古納網(wǎng)站建設(shè)費(fèi)用合理。
語(yǔ)法糖
語(yǔ)法糖(Syntactic Sugar),也稱糖衣語(yǔ)法,是由英國(guó)計(jì)算機(jī)學(xué)家 Peter.J.Landin 發(fā)明的一個(gè)術(shù)語(yǔ),指在計(jì)算機(jī)語(yǔ)言中添加的某種語(yǔ)法,這種語(yǔ)法對(duì)語(yǔ)言的功能并沒(méi)有影響,但是更方便程序員使用。簡(jiǎn)而言之,語(yǔ)法糖讓程序更加簡(jiǎn)潔,有更高的可讀性。
有意思的是,在編程領(lǐng)域,除了語(yǔ)法糖,還有語(yǔ)法鹽和語(yǔ)法糖精的說(shuō)法,篇幅有限這里不做擴(kuò)展了。
我們所熟知的編程語(yǔ)言中幾乎都有語(yǔ)法糖。作者認(rèn)為,語(yǔ)法糖的多少是評(píng)判一個(gè)語(yǔ)言夠不夠牛逼的標(biāo)準(zhǔn)之一。
很多人說(shuō)Java是一個(gè)“低糖語(yǔ)言”,其實(shí)從Java 7開(kāi)始Java語(yǔ)言層面上一直在添加各種糖,主要是在“Project Coin”項(xiàng)目下研發(fā)。盡管現(xiàn)在Java有人還是認(rèn)為現(xiàn)在的Java是低糖,未來(lái)還會(huì)持續(xù)向著“高糖”的方向發(fā)展。
解語(yǔ)法糖
前面提到過(guò),語(yǔ)法糖的存在主要是方便開(kāi)發(fā)人員使用。但其實(shí),Java虛擬機(jī)并不支持這些語(yǔ)法糖。這些語(yǔ)法糖在編譯階段就會(huì)被還原成簡(jiǎn)單的基礎(chǔ)語(yǔ)法結(jié)構(gòu),這個(gè)過(guò)程就是解語(yǔ)法糖。
說(shuō)到編譯,大家肯定都知道,Java語(yǔ)言中,javac命令可以將后綴名為.java的源文件編譯為后綴名為.class的可以運(yùn)行于Java虛擬機(jī)的字節(jié)碼。
如果你去看com.sun.tools.javac.main.JavaCompiler的源碼,你會(huì)發(fā)現(xiàn)在compile()中有一個(gè)步驟就是調(diào)用desugar(),這個(gè)方法就是負(fù)責(zé)解語(yǔ)法糖的實(shí)現(xiàn)的。
Java中最常用的語(yǔ)法糖主要有泛型、變長(zhǎng)參數(shù)、條件編譯、自動(dòng)拆裝箱、內(nèi)部類等。本文主要來(lái)分析下這些語(yǔ)法糖背后的原理。一步一步剝?nèi)ヌ且?,看看其本質(zhì)。
糖塊一、 switch 支持 String 與枚舉
前面提到過(guò),從Java 7 開(kāi)始,Java語(yǔ)言中的語(yǔ)法糖在逐漸豐富,其中一個(gè)比較重要的就是Java 7中switch開(kāi)始支持String。
在開(kāi)始coding之前先科普下,Java中的swith自身原本就支持基本類型。比如int、char等。
對(duì)于int類型,直接進(jìn)行數(shù)值的比較。對(duì)于char類型則是比較其ascii碼。
所以,對(duì)于編譯器來(lái)說(shuō),switch中其實(shí)只能使用整型,任何類型的比較都要轉(zhuǎn)換成整型。比如byte。short,char(ackii碼是整型)以及int。
那么接下來(lái)看下switch對(duì)String得支持,有以下代碼:
public class switchDemoString { public static void main(String[] args) { String str = "world"; switch (str) { case "hello": System.out.println("hello"); break; case "world": System.out.println("world"); break; default: break; } } }
反編譯后內(nèi)容如下:
public class switchDemoString { public switchDemoString() { } public static void main(String args[]) { String str = "world"; String s; switch((s = str).hashCode()) { default: break; case 99162322: if(s.equals("hello")) System.out.println("hello"); break; case 113318802: if(s.equals("world")) System.out.println("world"); break; } } }
看到這個(gè)代碼,你知道原來(lái)字符串的switch是通過(guò)equals()和hashCode()方法來(lái)實(shí)現(xiàn)的。還好hashCode()方法返回的是int,而不是long。
仔細(xì)看下可以發(fā)現(xiàn),進(jìn)行switch的實(shí)際是哈希值,然后通過(guò)使用equals方法比較進(jìn)行安全檢查,這個(gè)檢查是必要的,因?yàn)楣?赡軙?huì)發(fā)生碰撞。因此它的性能是不如使用枚舉進(jìn)行switch或者使用純整數(shù)常量,但這也不是很差。
糖塊二、 泛型
我們都知道,很多語(yǔ)言都是支持泛型的,但是很多人不知道的是,不同的編譯器對(duì)于泛型的處理方式是不同的。
通常情況下,一個(gè)編譯器處理泛型有兩種方式:Code specialization和Code sharing。
C++和C#是使用Code specialization的處理機(jī)制,而Java使用的是Code sharing的機(jī)制。
Code sharing方式為每個(gè)泛型類型創(chuàng)建唯一的字節(jié)碼表示,并且將該泛型類型的實(shí)例都映射到這個(gè)唯一的字節(jié)碼表示上。將多種泛型類形實(shí)例映射到唯一的字節(jié)碼表示是通過(guò)類型擦除(type erasue)實(shí)現(xiàn)的。
Code sharing方式為每個(gè)泛型類型創(chuàng)建唯一的字節(jié)碼表示,并且將該泛型類型的實(shí)例都映射到這個(gè)唯一的字節(jié)碼表示上。將多種泛型類形實(shí)例映射到唯一的字節(jié)碼表示是通過(guò)類型擦除(type erasue)實(shí)現(xiàn)的。
類型擦除的主要過(guò)程如下:
1.將所有的泛型參數(shù)用其最左邊界(最頂級(jí)的父類型)類型替換。
2.移除所有的類型參數(shù)。
以下代碼:
Mapmap = new HashMap (); map.put("name", "hollis"); map.put("wechat", "Hollis"); map.put("blog", "www.hollischuang.com");
解語(yǔ)法糖之后會(huì)變成:
Map map = new HashMap(); map.put("name", "hollis"); map.put("wechat", "Hollis"); map.put("blog", "www.hollischuang.com");
以下代碼:
public static > A max(Collection xs) { Iterator xi = xs.iterator(); A w = xi.next(); while (xi.hasNext()) { A x = xi.next(); if (w.compareTo(x) < 0) w = x; } return w; }
public static Comparable max(Collection xs) { Iterator xi = xs.iterator(); Comparable w = (Comparable)xi.next(); while(xi.hasNext()) { Comparable x = (Comparable)xi.next(); if(w.compareTo(x) < 0) w = x; } return w; }
因?yàn)檫@里的裝箱和拆箱是自動(dòng)進(jìn)行的非人為轉(zhuǎn)換,所以就稱作為自動(dòng)裝箱和拆箱。
public static void main(String[] args) { int i = 10; Integer n = i; }
public static void main(String args[]) { int i = 10; Integer n = Integer.valueOf(i); }
public static void main(String[] args) { Integer i = 10; int n = i; }
public static void main(String args[]) { Integer i = Integer.valueOf(10); int n = i.intValue(); }
可變參數(shù)(variable arguments)是在Java 1.5中引入的一個(gè)特性。它允許一個(gè)方法把任意數(shù)量的值作為參數(shù)。
看下以下可變參數(shù)代碼,其中print方法接收可變參數(shù):
public static void main(String[] args) { print("Holis", "公眾號(hào):Hollis", "博客:www.hollischuang.com", "QQ:907607222"); } public static void print(String... strs) { for (int i = 0; i < strs.length; i++) { System.out.println(strs[i]); } }
public static void main(String args[]) { print(new String[] { "Holis", "\u516C\u4F17\u53F7:Hollis", "\u535A\u5BA2\uFF1Awww.hollischuang.com","QQ\uFF1A907607222" }); } public static transient void print(String strs[]) { for(int i = 0; i < strs.length; i++) System.out.println(strs[i]); }
答案很明顯不是,enum就和class一樣,只是一個(gè)關(guān)鍵字,他并不是一個(gè)類。
那么枚舉是由什么類維護(hù)的呢,我們簡(jiǎn)單的寫(xiě)一個(gè)枚舉:
public enum t { SPRING,SUMMER; } 然后我們使用反編譯,看看這段代碼到底是怎么實(shí)現(xiàn)的,反編譯后代碼內(nèi)容如下: public final class T extends Enum { private T(String s, int i) { super(s, i); } public static T[] values() { T at[]; int i; T at1[]; System.arraycopy(at = ENUM$VALUES, 0, at1 = new T[i = at.length], 0, i); return at1; } public static T valueOf(String s) { return (T)Enum.valueOf(demo/T, s); } public static final T SPRING; public static final T SUMMER; private static final T ENUM$VALUES[]; static { SPRING = new T("SPRING", 0); SUMMER = new T("SUMMER", 1); ENUM$VALUES = (new T[] { SPRING, SUMMER }); } }
內(nèi)部類又稱為嵌套類,可以把內(nèi)部類理解為外部類的一個(gè)普通成員。
內(nèi)部類之所以也是語(yǔ)法糖,是因?yàn)樗鼉H僅是一個(gè)編譯時(shí)的概念。
public class OutterClass { private String userName; public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public static void main(String[] args) { } class InnerClass{ private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } } }
以上代碼編譯后會(huì)生成兩個(gè)class文件:OutterClass$InnerClass.class 、OutterClass.class 。
當(dāng)我們嘗試使用jad對(duì)OutterClass.class文件進(jìn)行反編譯的時(shí)候,命令行會(huì)打印以下內(nèi)容:
他會(huì)把兩個(gè)文件全部進(jìn)行反編譯,然后一起生成一個(gè)OutterClass.jad文件。文件內(nèi)容如下:
public class OutterClass { class InnerClass { public String getName() { return name; } public void setName(String name) { this.name = name; } private String name; final OutterClass this$0; InnerClass() { this.this$0 = OutterClass.this; super(); } } public OutterClass() { } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public static void main(String args1[]) { } private String userName; }
public class ConditionalCompilation { public static void main(String[] args) { final boolean DEBUG = true; if(DEBUG) { System.out.println("Hello, DEBUG!"); } final boolean ONLINE = false; if(ONLINE){ System.out.println("Hello, ONLINE!"); } } }
public class ConditionalCompilation { public ConditionalCompilation() { } public static void main(String args[]) { boolean DEBUG = true; System.out.println("Hello, DEBUG!"); boolean ONLINE = false; } }
這與C/C++的條件編譯相比,確實(shí)更有局限性。在Java語(yǔ)言設(shè)計(jì)之初并沒(méi)有引入條件編譯的功能,雖有局限,但是總比沒(méi)有更強(qiáng)。
如果要開(kāi)啟斷言檢查,則需要用開(kāi)關(guān)-enableassertions或-ea來(lái)開(kāi)啟。
public class AssertTest { public static void main(String args[]) { int a = 1; int b = 1; assert a == b; System.out.println("公眾號(hào):Hollis"); assert a != b : "Hollis"; System.out.println("博客:www.hollischuang.com"); } }
public class AssertTest { public AssertTest() { } public static void main(String args[]) { int a = 1; int b = 1; if (!$assertionsDisabled && a != b) throw new AssertionError(); System.out.println("\u516C\u4F17\u53F7\uFF1AHollis"); if(!$assertionsDisabled && a == b) { throw new AssertionError("Hollis"); } else { System.out.println("\u535A\u5BA2\uFF1Awww.hollischuang.com"); return; } } static final boolean $assertionsDisabled = !com/hollis/suguar/AssertTest.desiredAssertionStatus(); }
很明顯,反編譯之后的代碼要比我們自己的代碼復(fù)雜的多。所以,使用了assert這個(gè)語(yǔ)法糖我們節(jié)省了很多代碼。
-enableassertions會(huì)設(shè)置$assertionsDisabled字段的值。
public class Test { public static void main(String... args) { int i = 10_000; System.out.println(i); } }
public class Test { public static void main(String[] args) { int i = 10000; System.out.println(i); } }
反編譯后就是把_刪除了。也就是說(shuō)編譯器并不認(rèn)識(shí)在數(shù)字字面量中的_,需要在編譯階段把他去掉。
public static void main(String... args) { String[] strs = {"Hollis", "公眾號(hào):Hollis", "博客:www.hollischuang.com"}; for (String s : strs) { System.out.println(s); } ListstrList = ImmutableList.of("Hollis", "公眾號(hào):Hollis", "博客:www.hollischuang.com"); for (String s : strList) { System.out.println(s); } }
public static transient void main(String args[]) { String strs[] = { "Hollis", "\u516C\u4F17\u53F7\uFF1AHollis", "\u535A\u5BA2\uFF1Awww.hollischuang.com" }; 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("Hollis", "\u516C\u4F17\u53F7\uFF1AHollis", "\u535A\u5BA2\uFF1Awww.hollischuang.com"); String s; for(Iterator iterator = strList.iterator(); iterator.hasNext(); System.out.println(s)) s = (String)iterator.next(); }
代碼很簡(jiǎn)單,for-each的實(shí)現(xiàn)原理其實(shí)就是使用了普通的for循環(huán)和迭代器。
關(guān)閉資源的常用方式就是在finally塊里是釋放,即調(diào)用close方法。比如,我們經(jīng)常會(huì)寫(xiě)這樣的代碼:
public static void main(String[] args) { BufferedReader br = null; try { String line; br = new BufferedReader(new FileReader("d:\\hollischuang.xml")); while ((line = br.readLine()) != null) { System.out.println(line); } } catch (IOException e) { // handle exception } finally { try { if (br != null) { br.close(); } } catch (IOException ex) { // handle exception } } }
從Java 7開(kāi)始,jdk提供了一種更好的方式關(guān)閉資源,使用try-with-resources語(yǔ)句,改寫(xiě)一下上面的代碼,效果如下:
public static void main(String... args) { try (BufferedReader br = new BufferedReader(new FileReader("d:\\ hollischuang.xml"))) { String line; while ((line = br.readLine()) != null) { System.out.println(line); } } catch (IOException e) { // handle exception } }
public static transient void main(String args[]) { BufferedReader br; Throwable throwable; br = new BufferedReader(new FileReader("d:\\ hollischuang.xml")); throwable = null; String line; try { while((line = br.readLine()) != null) System.out.println(line); } catch(Throwable throwable2) { throwable = throwable2; throw throwable2; } if(br != null) if(throwable != null) try { br.close(); } catch(Throwable throwable1) { throwable.addSuppressed(throwable1); } else br.close(); break MISSING_BLOCK_LABEL_113; Exception exception; exception; if(br != null) if(throwable != null) try { br.close(); } catch(Throwable throwable3) { throwable.addSuppressed(throwable3); } else br.close(); throw exception; IOException ioexception; ioexception; } }
其實(shí)背后的原理也很簡(jiǎn)單,那些我們沒(méi)有做的關(guān)閉資源的操作,編譯器都幫我們做了。
所以,再次印證了,語(yǔ)法糖的作用就是方便程序員的使用,但最終還是要轉(zhuǎn)成編譯器認(rèn)識(shí)的語(yǔ)言。
先來(lái)看一個(gè)簡(jiǎn)單的lambda表達(dá)式。遍歷一個(gè)list:
public static void main(String... args) { ListstrList = ImmutableList.of("Hollis", "公眾號(hào):Hollis", "博客:www.hollischuang.com"); strList.forEach( s -> { System.out.println(s); } ); }
public static /* varargs */ void main(String ... args) { ImmutableList strList = ImmutableList.of((Object)"Hollis", (Object)"\u516c\u4f17\u53f7\uff1aHollis", (Object)"\u535a\u5ba2\uff1awww.hollischuang.com"); strList.forEach((Consumer)LambdaMetafactory.metafactory(null, null, null, (Ljava/lang/Object;)V, lambda$main$0(java.lang.String ), (Ljava/lang/String;)V)()); } private static /* synthetic */ void lambda$main$0(String s) { System.out.println(s); }
再來(lái)看一個(gè)稍微復(fù)雜一點(diǎn)的,先對(duì)List進(jìn)行過(guò)濾,然后再輸出:
public static void main(String... args) { ListstrList = ImmutableList.of("Hollis", "公眾號(hào):Hollis", "博客:www.hollischuang.com"); List HollisList = strList.stream().filter(string -> string.contains("Hollis")).collect(Collectors.toList()); HollisList.forEach( s -> { System.out.println(s); } ); }
public static /* varargs */ void main(String ... args) { ImmutableList strList = ImmutableList.of((Object)"Hollis", (Object)"\u516c\u4f17\u53f7\uff1aHollis", (Object)"\u535a\u5ba2\uff1awww.hollischuang.com"); List