摘要:但其實(shí),虛擬機(jī)并不支持這些語法糖。方式為每個(gè)泛型類型創(chuàng)建唯一的字節(jié)碼表示,并且將該泛型類型的實(shí)例都映射到這個(gè)唯一的字節(jié)碼表示上。
GitHub 2.5k Star 的Java工程師成神之路 ,不來了解一下嗎);
GitHub 2.5k Star 的Java工程師成神之路 ,真的不來了解一下嗎);
GitHub 2.5k Star 的Java工程師成神之路 ,真的確定不來了解一下嗎);
本文從 Java 編譯原理角度,深入字節(jié)碼及 class 文件,抽絲剝繭,了解 Java 中的語法糖原理及用法,幫助大家在學(xué)會(huì)如何使用 Java。
語法糖語法糖(Syntactic Sugar),也稱糖衣語法,是由英國計(jì)算機(jī)學(xué)家 Peter.J.Landin 發(fā)明的一個(gè)術(shù)語,指在計(jì)算機(jī)語言中添加的某種語法,這種語法對(duì)語言的功能并沒有影響,但是更方便程序員使用。簡而言之,語法糖讓程序更加簡潔,有更高的可讀性。
有意思的是,在編程領(lǐng)域,除了語法糖,還有語法鹽和語法糖精的說法,篇幅有限這里不做擴(kuò)展了。
我們所熟知的編程語言中幾乎都有語法糖。作者認(rèn)為,語法糖的多少是評(píng)判一個(gè)語言夠不夠牛逼的標(biāo)準(zhǔn)之一。很多人說Java是一個(gè)“低糖語言”,其實(shí)從Java 7開始Java語言層面上一直在添加各種糖,主要是在“Project Coin”項(xiàng)目下研發(fā)。盡管現(xiàn)在Java有人還是認(rèn)為現(xiàn)在的Java是低糖,未來還會(huì)持續(xù)向著“高糖”的方向發(fā)展。
解語法糖前面提到過,語法糖的存在主要是方便開發(fā)人員使用。但其實(shí),Java虛擬機(jī)并不支持這些語法糖。這些語法糖在編譯階段就會(huì)被還原成簡單的基礎(chǔ)語法結(jié)構(gòu),這個(gè)過程就是解語法糖。
說到編譯,大家肯定都知道,Java語言中,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é)解語法糖的實(shí)現(xiàn)的。
Java 中最常用的語法糖主要有泛型、變長參數(shù)、條件編譯、自動(dòng)拆裝箱、內(nèi)部類等。本文主要來分析下這些語法糖背后的原理。一步一步剝?nèi)ヌ且?,看看其本質(zhì)。
糖塊一、 switch 支持 String 與枚舉前面提到過,從Java 7 開始,Java語言中的語法糖在逐漸豐富,其中一個(gè)比較重要的就是Java 7中switch開始支持String。
在開始coding之前先科普下,Java中的swith自身原本就支持基本類型。比如int、char等。對(duì)于int類型,直接進(jìn)行數(shù)值的比較。對(duì)于char類型則是比較其ascii碼。所以,對(duì)于編譯器來說,switch中其實(shí)只能使用整型,任何類型的比較都要轉(zhuǎn)換成整型。比如byte。short,char(ackii碼是整型)以及int。
那么接下來看下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è)代碼,你知道原來**字符串的switch是通過equals()和hashCode()方法來實(shí)現(xiàn)的。**還好hashCode()方法返回的是int,而不是long。
糖塊二、 泛型仔細(xì)看下可以發(fā)現(xiàn),進(jìn)行switch的實(shí)際是哈希值,然后通過使用equals方法比較進(jìn)行安全檢查,這個(gè)檢查是必要的,因?yàn)楣?赡軙?huì)發(fā)生碰撞。因此它的性能是不如使用枚舉進(jìn)行switch或者使用純整數(shù)常量,但這也不是很差。
我們都知道,很多語言都是支持泛型的,但是很多人不知道的是,不同的編譯器對(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é)碼表示是通過類型擦除(type erasue)實(shí)現(xiàn)的。
也就是說,對(duì)于Java虛擬機(jī)來說,他根本不認(rèn)識(shí)Map
類型擦除的主要過程如下: 1.將所有的泛型參數(shù)用其最左邊界(最頂級(jí)的父類型)類型替換。 2.移除所有的類型參數(shù)。
以下代碼:
Mapmap = new HashMap (); map.put("name", "hollis"); map.put("wechat", "Hollis"); map.put("blog", "www.hollischuang.com");
解語法糖之后會(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; }
類型擦除后會(huì)變成:
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; }
虛擬機(jī)中沒有泛型,只有普通類和普通方法,所有泛型類的類型參數(shù)在編譯時(shí)都會(huì)被擦除,泛型類并沒有自己獨(dú)有的Class類對(duì)象。比如并不存在List
自動(dòng)裝箱就是Java自動(dòng)將原始類型值轉(zhuǎn)換成對(duì)應(yīng)的對(duì)象,比如將int的變量轉(zhuǎn)換成Integer對(duì)象,這個(gè)過程叫做裝箱,反之將Integer對(duì)象轉(zhuǎn)換成int類型值,這個(gè)過程叫做拆箱。因?yàn)檫@里的裝箱和拆箱是自動(dòng)進(jìn)行的非人為轉(zhuǎn)換,所以就稱作為自動(dòng)裝箱和拆箱。原始類型byte, short, char, int, long, float, double 和 boolean 對(duì)應(yīng)的封裝類為Byte, Short, Character, Integer, Long, Float, Double, Boolean。
先來看個(gè)自動(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); }
再來看個(gè)自動(dòng)拆箱的代碼:
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(); }
從反編譯得到內(nèi)容可以看出,在裝箱的時(shí)候自動(dòng)調(diào)用的是Integer的valueOf(int)方法。而在拆箱的時(shí)候自動(dòng)調(diào)用的是Integer的intValue方法。
所以,裝箱過程是通過調(diào)用包裝器的valueOf方法實(shí)現(xiàn)的,而拆箱過程是通過調(diào)用包裝器的 xxxValue方法實(shí)現(xiàn)的。
糖塊四 、 方法變長參數(shù)可變參數(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", "u516Cu4F17u53F7:Hollis", "u535Au5BA2uFF1Awww.hollischuang.com", "QQuFF1A907607222" }); } public static transient void print(String strs[]) { for(int i = 0; i < strs.length; i++) System.out.println(strs[i]); }
從反編譯后代碼可以看出,可變參數(shù)在被使用的時(shí)候,他首先會(huì)創(chuàng)建一個(gè)數(shù)組,數(shù)組的長度就是調(diào)用該方法是傳遞的實(shí)參的個(gè)數(shù),然后再把參數(shù)值全部放到這個(gè)數(shù)組當(dāng)中,然后再把這個(gè)數(shù)組作為參數(shù)傳遞到被調(diào)用的方法中。
糖塊五 、 枚舉PS:反編譯后的print方法聲明中有一個(gè)transient標(biāo)識(shí),是不是很奇怪?transient不是不可以修飾方法嗎?transient不是和序列化有關(guān)么?transient在這里的作用是什么?因?yàn)檫@個(gè)與本文關(guān)系不大,這里不做深入分析了。相了解的同學(xué)可以關(guān)注我微信公眾號(hào)或者博客。
Java SE5提供了一種新的類型-Java的枚舉類型,關(guān)鍵字enum可以將一組具名的值的有限集合創(chuàng)建為一種新的類型,而這些具名的值可以作為常規(guī)的程序組件使用,這是一種非常有用的功能。
要想看源碼,首先得有一個(gè)類吧,那么枚舉類型到底是什么類呢?是enum嗎?答案很明顯不是,enum就和class一樣,只是一個(gè)關(guān)鍵字,他并不是一個(gè)類,那么枚舉是由什么類維護(hù)的呢,我們簡單的寫一個(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 }); } }
通過反編譯后代碼我們可以看到,public final class T extends Enum,說明,該類是繼承了Enum類的,同時(shí)final關(guān)鍵字告訴我們,這個(gè)類也是不能被繼承的。當(dāng)我們使用enmu來定義一個(gè)枚舉類型的時(shí)候,編譯器會(huì)自動(dòng)幫我們創(chuàng)建一個(gè)final類型的類繼承Enum類,所以枚舉類型不能被繼承。
糖塊六 、 內(nèi)部類內(nèi)部類又稱為嵌套類,可以把內(nèi)部類理解為外部類的一個(gè)普通成員。
內(nèi)部類之所以也是語法糖,是因?yàn)樗鼉H僅是一個(gè)編譯時(shí)的概念,outer.java里面定義了一個(gè)內(nèi)部類inner,一旦編譯成功,就會(huì)生成兩個(gè)完全不同的.class文件了,分別是outer.class和outer$inner.class。所以內(nèi)部類的名字完全可以和它的外部類名字相同。
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)我們嘗試對(duì)OutterClass.class文件進(jìn)行反編譯的時(shí)候,命令行會(huì)打印以下內(nèi)容:Parsing OutterClass.class...Parsing inner class OutterClass$InnerClass.class... Generating OutterClass.jad 。他會(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; }糖塊七 、條件編譯
—般情況下,程序中的每一行代碼都要參加編譯。但有時(shí)候出于對(duì)程序代碼優(yōu)化的考慮,希望只對(duì)其中一部分內(nèi)容進(jìn)行編譯,此時(shí)就需要在程序中加上條件,讓編譯器只對(duì)滿足條件的代碼進(jìn)行編譯,將不滿足條件的代碼舍棄,這就是條件編譯。
如在C或CPP中,可以通過預(yù)處理語句來實(shí)現(xiàn)條件編譯。其實(shí)在Java中也可實(shí)現(xiàn)條件編譯。我們先來看一段代碼:
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; } }
首先,我們發(fā)現(xiàn),在反編譯后的代碼中沒有System.out.println("Hello, ONLINE!");,這其實(shí)就是條件編譯。當(dāng)if(ONLINE)為false的時(shí)候,編譯器就沒有對(duì)其內(nèi)的代碼進(jìn)行編譯。
所以,Java語法的條件編譯,是通過判斷條件為常量的if語句實(shí)現(xiàn)的。其原理也是Java語言的語法糖。根據(jù)if判斷條件的真假,編譯器直接把分支為false的代碼塊消除。通過該方式實(shí)現(xiàn)的條件編譯,必須在方法體內(nèi)實(shí)現(xiàn),而無法在正整個(gè)Java類的結(jié)構(gòu)或者類的屬性上進(jìn)行條件編譯,這與C/C++的條件編譯相比,確實(shí)更有局限性。在Java語言設(shè)計(jì)之初并沒有引入條件編譯的功能,雖有局限,但是總比沒有更強(qiáng)。
糖塊八 、 斷言在Java中,assert關(guān)鍵字是從JAVA SE 1.4 引入的,為了避免和老版本的Java代碼中使用了assert關(guān)鍵字導(dǎo)致錯(cuò)誤,Java在執(zhí)行的時(shí)候默認(rèn)是不啟動(dòng)斷言檢查的(這個(gè)時(shí)候,所有的斷言語句都將忽略?。?,如果要開啟斷言檢查,則需要用開關(guān)-enableassertions或-ea來開啟。
看一段包含斷言的代碼:
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("u516Cu4F17u53F7uFF1AHollis"); if(!$assertionsDisabled && a == b) { throw new AssertionError("Hollis"); } else { System.out.println("u535Au5BA2uFF1Awww.hollischuang.com"); return; } } static final boolean $assertionsDisabled = !com/hollis/suguar/AssertTest.desiredAssertionStatus(); }
很明顯,反編譯之后的代碼要比我們自己的代碼復(fù)雜的多。所以,使用了assert這個(gè)語法糖我們節(jié)省了很多代碼。其實(shí)斷言的底層實(shí)現(xiàn)就是if語言,如果斷言結(jié)果為true,則什么都不做,程序繼續(xù)執(zhí)行,如果斷言結(jié)果為false,則程序拋出AssertError來打斷程序的執(zhí)行。-enableassertions會(huì)設(shè)置$assertionsDisabled字段的值。
糖塊九 、 數(shù)值字面量在java 7中,數(shù)值字面量,不管是整數(shù)還是浮點(diǎn)數(shù),都允許在數(shù)字之間插入任意多個(gè)下劃線。這些下劃線不會(huì)對(duì)字面量的數(shù)值產(chǎn)生影響,目的就是方便閱讀。
比如:
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); } }
反編譯后就是把_刪除了。也就是說 編譯器并不認(rèn)識(shí)在數(shù)字字面量中的_,需要在編譯階段把他去掉。
糖塊十 、 for-each增強(qiáng)for循環(huán)(for-each)相信大家都不陌生,日常開發(fā)經(jīng)常會(huì)用到的,他會(huì)比for循環(huán)要少寫很多代碼,那么這個(gè)語法糖背后是如何實(shí)現(xiàn)的呢?
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", "u516Cu4F17u53F7uFF1AHollis", "u535Au5BA2uFF1Awww.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", "u516Cu4F17u53F7uFF1AHollis", "u535Au5BA2uFF1Awww.hollischuang.com"); String s; for(Iterator iterator = strList.iterator(); iterator.hasNext(); System.out.println(s)) s = (String)iterator.next(); }
代碼很簡單,for-each的實(shí)現(xiàn)原理其實(shí)就是使用了普通的for循環(huán)和迭代器。
糖塊十一 、 try-with-resourceJava里,對(duì)于文件操作IO流、數(shù)據(jù)庫連接等開銷非常昂貴的資源,用完之后必須及時(shí)通過close方法將其關(guān)閉,否則資源會(huì)一直處于打開狀態(tài),可能會(huì)導(dǎo)致內(nèi)存泄露等問題。
關(guān)閉資源的常用方式就是在finally塊里是釋放,即調(diào)用close方法。比如,我們經(jīng)常會(huì)寫這樣的代碼:
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開始,jdk提供了一種更好的方式關(guān)閉資源,使用try-with-resources語句,改寫一下上面的代碼,效果如下:
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 } }
看,這簡直是一大福音啊,雖然我之前一般使用IOUtils去關(guān)閉流,并不會(huì)使用在finally中寫很多代碼的方式,但是這種新的語法糖看上去好像優(yōu)雅很多呢??聪滤谋澈螅?/p>
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í)背后的原理也很簡單,那些我們沒有做的關(guān)閉資源的操作,編譯器都幫我們做了。所以,再次印證了,語法糖的作用就是方便程序員的使用,但最終還是要轉(zhuǎn)成編譯器認(rèn)識(shí)的語言。
糖塊十二、Lambda表達(dá)式關(guān)于lambda表達(dá)式,有人可能會(huì)有質(zhì)疑,因?yàn)榫W(wǎng)上有人說他并不是語法糖。其實(shí)我想糾正下這個(gè)說法。Labmda表達(dá)式不是匿名內(nèi)部類的語法糖,但是他也是一個(gè)語法糖。實(shí)現(xiàn)方式其實(shí)是依賴了幾個(gè)JVM底層提供的lambda相關(guān)api。
先來看一個(gè)簡單的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); } ); }
為啥說他并不是內(nèi)部類的語法糖呢,前面講內(nèi)部類我們說過,內(nèi)部類在編譯之后會(huì)有兩個(gè)class文件,但是,包含lambda表達(dá)式的類編譯后只有一個(gè)文件。
反編譯后代碼如下:
public static /* varargs */ void main(String ... args) { ImmutableList strList = ImmutableList.of((Object)"Hollis", (Object)"u516cu4f17u53f7uff1aHollis", (Object)"u535au5ba2uff1awww.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); }
可以看到,在forEach方法中,其實(shí)是調(diào)用了java.lang.invoke.LambdaMetafactory#metafactory方法,該方法的第四個(gè)參數(shù)implMethod指定了方法實(shí)現(xiàn)??梢钥吹竭@里其實(shí)是調(diào)用了一個(gè)lambda$main$0方法進(jìn)行了輸出。
再來看一個(gè)稍微復(fù)雜一點(diǎn)的,先對(duì)List進(jìn)行過濾,然后再輸出:
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)"u516cu4f17u53f7uff1aHollis", (Object)"u535au5ba2uff1awww.hollischuang.com"); List
兩個(gè)lambda表達(dá)式分別調(diào)用了lambda$main$1和lambda$main$0兩個(gè)方法。
所以,lambda表達(dá)式的實(shí)現(xiàn)其實(shí)是依賴了一些底層的api,在編譯階段,編譯器會(huì)把lambda表達(dá)式進(jìn)行解糖,轉(zhuǎn)換成調(diào)用內(nèi)部api的方式。
可能遇到的坑一、當(dāng)泛型遇到重載 public class GenericTypes {
public static void method(Listlist) { System.out.println("invoke method(List list)"); } public static void method(List list) { System.out.println("invoke method(List list)"); } }
上面這段代碼,有兩個(gè)重載的函數(shù),因?yàn)樗麄兊膮?shù)類型不同,一個(gè)是List另一個(gè)是List ,但是,這段代碼是編譯通不過的。因?yàn)槲覀兦懊嬷v過,參數(shù)List和List編譯之后都被擦除了,變成了一樣的原生類型List,擦除動(dòng)作導(dǎo)致這兩個(gè)方法的特征簽名變得一模一樣。
二、當(dāng)泛型遇到catch 泛型的類型參數(shù)不能用在Java異常處理的catch語句中。因?yàn)楫惓L幚硎怯蒍VM在運(yùn)行時(shí)刻來進(jìn)行的。由于類型信息被擦除,JVM是無法區(qū)分兩個(gè)異常類型MyException
三、當(dāng)泛型內(nèi)包含靜態(tài)變量
public class StaticTest{ public static void main(String[] args){ GTgti = new GT (); gti.var=1; GT gts = new GT (); gts.var=2; System.out.println(gti.var); } } class GT { public static int var=0; public void nothing(T x){} }
以上代碼輸出結(jié)果為:2!由于經(jīng)過類型擦除,所有的泛型類實(shí)例都關(guān)聯(lián)到同一份字節(jié)碼上,泛型類的所有靜態(tài)變量是共享的。
對(duì)象相等比較
public class BoxingTest {
public static void main(String[] args) { Integer a = 1000; Integer b = 1000; Integer c = 100; Integer d = 100; System.out.println("a == b is " + (a == b)); System.out.println(("c == d is " + (c == d))); }
輸出結(jié)果:
a == b is false c == d is true
在Java 5中,在Integer的操作上引入了一個(gè)新功能來節(jié)省內(nèi)存和提高性能。整型對(duì)象通過使用相同的對(duì)象引用實(shí)現(xiàn)了緩存和重用。
適用于整數(shù)值區(qū)間-128 至 +127。
只適用于自動(dòng)裝箱。使用構(gòu)造函數(shù)創(chuàng)建對(duì)象不適用。
ConcurrentModificationException
for (Student stu : students) { if (stu.getId() == 2) students.remove(stu); }
會(huì)拋出ConcurrentModificationException異常。
Iterator是工作在一個(gè)獨(dú)立的線程中,并且擁有一個(gè) mutex 鎖。 Iterator被創(chuàng)建之后會(huì)建立一個(gè)指向原來對(duì)象的單鏈索引表,當(dāng)原來的對(duì)象數(shù)量發(fā)生變化時(shí),這個(gè)索引表的內(nèi)容不會(huì)同步改變,所以當(dāng)索引指針往后移動(dòng)的時(shí)候就找不到要迭代的對(duì)象,所以按照 fail-fast 原則 Iterator 會(huì)馬上拋出java.util.ConcurrentModificationException異常。
所以 Iterator 在工作的時(shí)候是不允許被迭代的對(duì)象被改變的。但你可以使用 Iterator 本身的方法remove()來刪除對(duì)象,Iterator.remove() 方法會(huì)在刪除當(dāng)前迭代對(duì)象的同時(shí)維護(hù)索引的一致性。
總結(jié)前面介紹了12種Java中常用的語法糖。所謂語法糖就是提供給開發(fā)人員便于開發(fā)的一種語法而已。但是這種語法只有開發(fā)人員認(rèn)識(shí)。要想被執(zhí)行,需要進(jìn)行解糖,即轉(zhuǎn)成JVM認(rèn)識(shí)的語法。當(dāng)我們把語法糖解糖之后,你就會(huì)發(fā)現(xiàn)其實(shí)我們?nèi)粘J褂玫倪@些方便的語法,其實(shí)都是一些其他更簡單的語法構(gòu)成的。
有了這些語法糖,我們?cè)谌粘i_發(fā)的時(shí)候可以大大提升效率,但是同時(shí)也要避免過渡使用。使用之前最好了解下原理,避免掉坑。
參考資料: Java的反編譯 Java中的Switch對(duì)整型、字符型、字符串型的具體實(shí)現(xiàn)細(xì)節(jié) 深度分析Java的枚舉類型—-枚舉的線程安全性及序列化問題 Java的枚舉類型用法介紹 Java中的增強(qiáng)for循環(huán)(for each)的實(shí)現(xiàn)原理與坑 Java中泛型的理解 Java中整型的緩存機(jī)制 Java中的可變參數(shù)
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/7153.html
摘要:但其實(shí),虛擬機(jī)并不支持這些語法糖。方式為每個(gè)泛型類型創(chuàng)建唯一的字節(jié)碼表示,并且將該泛型類型的實(shí)例都映射到這個(gè)唯一的字節(jié)碼表示上。GitHub 2.5k Star 的Java工程師成神之路 ,不來了解一下嗎); GitHub 2.5k Star 的Java工程師成神之路 ,真的不來了解一下嗎); GitHub 2.5k Star 的Java工程師成神之路 ,真的確定不來了解一下嗎); 本文從 ...
摘要:但其實(shí),虛擬機(jī)并不支持這些語法糖。方式為每個(gè)泛型類型創(chuàng)建唯一的字節(jié)碼表示,并且將該泛型類型的實(shí)例都映射到這個(gè)唯一的字節(jié)碼表示上。GitHub 2.5k Star 的Java工程師成神之路 ,不來了解一下嗎); GitHub 2.5k Star 的Java工程師成神之路 ,真的不來了解一下嗎); GitHub 2.5k Star 的Java工程師成神之路 ,真的確定不來了解一下嗎); 本文從 ...
摘要:使用可以方便的對(duì)字符串進(jìn)行拼接。該方法使用進(jìn)行聲明,說明是一個(gè)線程安全的方法。所以,阿里巴巴開發(fā)手冊(cè)建議循環(huán)體內(nèi),字符串的連接方式,使用的方法進(jìn)行擴(kuò)展。但是,還要強(qiáng)調(diào)的是如果不是在循環(huán)體中進(jìn)行字符串拼接的話,直接使用就好了。 摘要: 學(xué)習(xí)阿里巴巴Java開發(fā)手冊(cè)。 原文:為什么阿里巴巴不建議在for循環(huán)中使用+進(jìn)行字符串拼接 微信公眾號(hào):Hollis Fundebug經(jīng)授權(quán)轉(zhuǎn)載,...
摘要:為了減少在中創(chuàng)建的字符串的數(shù)量,字符串類維護(hù)了一個(gè)字符串常量池。但是當(dāng)執(zhí)行了方法后,將指向字符串常量池中的那個(gè)字符串常量。由于和都是字符串常量池中的字面量的引用,所以。究其原因,是因?yàn)槌A砍匾4娴氖且汛_定的字面量值。 String,是Java中除了基本數(shù)據(jù)類型以外,最為重要的一個(gè)類型了。很多人會(huì)認(rèn)為他比較簡單。但是和String有關(guān)的面試題有很多,下面我隨便找兩道面試題,看看你能不能...
摘要:結(jié)構(gòu)型模式適配器模式橋接模式裝飾模式組合模式外觀模式享元模式代理模式。行為型模式模版方法模式命令模式迭代器模式觀察者模式中介者模式備忘錄模式解釋器模式模式狀態(tài)模式策略模式職責(zé)鏈模式責(zé)任鏈模式訪問者模式。 主要版本 更新時(shí)間 備注 v1.0 2015-08-01 首次發(fā)布 v1.1 2018-03-12 增加新技術(shù)知識(shí)、完善知識(shí)體系 v2.0 2019-02-19 結(jié)構(gòu)...
閱讀 713·2023-04-25 19:43
閱讀 3910·2021-11-30 14:52
閱讀 3784·2021-11-30 14:52
閱讀 3852·2021-11-29 11:00
閱讀 3783·2021-11-29 11:00
閱讀 3869·2021-11-29 11:00
閱讀 3558·2021-11-29 11:00
閱讀 6105·2021-11-29 11:00