你不知道Java的10件事
你從一開始就在使用Java嗎?你是否還記得java被稱作為”Oak”的時期?那時,面向對象仍然是一個熱門的話題,使用C++的人們都認為Java沒有任何機會,Applets 也只是一件事情。
我敢打賭你肯定不知道以下一半的事情。現(xiàn)在,讓我們開始一些Java內(nèi)部運作的大驚喜。
1. 并沒有所謂的檢查異常沒錯,Java虛擬機(JVM)不知道異常,只有Java語言自己知道.
如今,每個人都同意檢查異常是一個錯誤。正如Bruce Eckel 在Prague的 GeeCON 閉幕詞上所說,在Java之后沒有其他語言會約定使用檢查異常,甚至 Java 8 新的流API都不再包含這些(在lambdas表達式中使用IO 或者JDBC時,是有點痛苦)。
(譯者注:Java8 引入了lambads表達式,使用它能夠使設計代碼更簡潔)
使用下面的代碼可以證明JVM并不知道這些:
上面的代碼不僅能通過編譯,而且拋出了異常SQLException,你甚至不需要使用Lombok’s 的注解@SneakyThrows。
(譯者注:Lombok是一個使用注解簡化Java代碼的庫)
(譯者注:如果你在方法中沒有聲明throws子句,當程序出現(xiàn)異常時Lomok 注解@SneakyThrows 會偷偷的拋出檢查異常)點這里獲取關于這篇文章的更多細節(jié),或 Stack Overflow。
2. 重載僅返回類型不同的方法這不能通過編譯,不是嗎?
是的。Java語言不允許在同一個類中存在"等價覆蓋" 的兩個方法.不管它們有不同的 throws 子句或是不同的返回類型。
在 Javadoc Class.getMethod(String,Class...)。上面有如下說明:
注意:它在一個類中可能會匹配到多個方法,雖然Java語言禁止在一個類中聲明多個簽名相同而僅返回類型不同的方法,但是Java虛擬機不會如此。在Java虛擬機中,這種增強 的靈活性被用于實現(xiàn)多樣的語言特性。例如,協(xié)變返回值類型能通過橋接方法實現(xiàn);橋接方法和被覆蓋的方法將有相同的簽名,不同的返回類型。
這很有意義,事實上,下面的語句所發(fā)生的幾乎就是這樣。
查看生成的字節(jié)碼:
因此,很好理解 T 在字節(jié)碼中就是一個對象。
這個合成的橋接方法實際上是由編譯器生成的,因為Parent.x()的返回類型簽名在某些調(diào)用位置可能會被期望成 Object。
加入的泛型沒有這樣的橋接方法就不可能以一種二進制的方式兼容。
因此,改變JVM去支持這種特性只需很少的代價(同樣也允許協(xié)變性壓倒一切負效應)很聰明,不是嗎?
你分析過語言的細節(jié)和內(nèi)幕嗎?點這里發(fā)現(xiàn)更多有趣的細節(jié)。
3. 這些都是二維數(shù)組這是真的。你可能無法立刻理解上面方法的返回類型,但它們都是一樣的。類似于下面的方法:
你肯定認為這瘋了。想象一下,為上面的方法使用 JSR-308 / Java 8 的類型注解。句法的數(shù)量將會激增。
你認為你在使用條件表達式時明白一切嗎?讓我告訴你吧,你不明白。大多數(shù)人都會認為下面兩個代碼片段是等價的:
真的一樣嗎?
不一樣。我們來驗證一下:
程序的輸出結果為:
1.01
沒錯!條件運算符在必要的時候將實現(xiàn)數(shù)據(jù)類型的提升。下面的語句將會拋出一個NullPointException。
點這里獲取更多細節(jié)。
5. 你也不明白復合賦值運算符看下面的代碼:
乍一看它們應該是等價的,但事實上不是。見 JSL(Java語言規(guī)范):
復合賦值表達式 E1 op= E2 與 E1 = (T)((E1) op (E2)) 是等價的,T的類型與E1相同,此外 E1僅計算一次。
這真是太美了,我想引用Peter Lawrey's 關于堆棧溢出問題的回答:
這是一個難題。不要參看解答,你能獨立的解決問題嗎?
運行下面的代碼:
我有時候會得到如下輸出:
922214548236183391933384
這怎么可能?
答案在這。通過反射覆蓋 JDK's 的 Integercache,然后使用自動裝箱和自動拆箱機制。
運行上面的代碼,你就可以得到類似的結果了。
7. GOTO在Java中編寫下列語句:
程序會編譯失敗,錯誤信息為:
因為goto是一個未使用的關鍵字。
雖然無法在源碼中直接使用 goto 但是我們可以通過 break,continue 和 標記塊實現(xiàn)。
字節(jié)碼的 goto;
向前跳轉:
它的字節(jié)碼為:
向后跳轉:
它的字節(jié)碼為:
看,是不是出現(xiàn)了goto
8. Java 的類型別名在其他語言中可以很簡單的使用類型別名,例如Ceylon:
(譯者注:Ceylon是一種新興的計算機編程語言,號稱"Java殺手",它不是Java,而是一種受Java影響的新語言。)
以這種方式構造的 People 可以被 Set<Person> 替換:
在Java中,我們無法在全局范圍上定義類型別名。由于存在 class 域或方法域,
亦可以定義。考慮兩個我們很不喜歡的命名 Integer 和 Long,為它們?nèi)€簡短的名稱 I 和 L:
上面的程序中,在 TestClass 域內(nèi) 定義 Integer 別名為 I,在 x() 方法域中定義Long
別名為 L。我們可以這樣使用上面的方法:
顯然這種技術不值得重視。在這個例子中,Integer 和 Long 都是 final 類型,也就意味著類型 I 和 L 是有效的別名(那樣的話,程序與類型兼容性也就無緣了)。如果我們使用的不是 final 類型,那么就應該使用泛型。
看夠了這些無聊的把戲了吧!來點厲害的。
9. 一些不可判定的關系類型讓我們來點咖啡,集中你的注意力,這可是很時髦的東西。考慮下面兩個類型:
現(xiàn)在,你知道 C 和 D 的類型嗎?
它們包含了遞歸,Java、lang、Enum 也是遞歸的。這兩種方式有些相似,但略有不同。
由上面的規(guī)范可知,Enum 實際上是由一種糖衣語法實現(xiàn)的。
(譯者注:糖衣語法,指計算機語言中添加的某種語法,這種語法對語言的功能并沒有影響,但是更方便程序員使用)
考慮到這一點,讓我們回到先前定義的兩個類型。下列代碼能編譯成功嗎?
很難回答,Ross Tate 有個答案,不可判定:
嘗試在你的Eclipse中編譯上面的代碼,崩潰了吧!
看這句話:
有些類型關系在Java中是不可判定的。
如果你有興趣了解這個奇怪的Java特性更多細節(jié),就讀一讀 Ross Tate的論文"Taming Wildcards in Java's Type System"(與 Alan Leung 和 Sorin Lerner 合著),或者自己思考關聯(lián)子類型多態(tài)性與泛型多態(tài)性。
10. 交集類型Java有一個很獨特的特性稱作交集類型(type intersections)。你可以聲明一個泛型,它由兩個類型的交集構成。例如:
要使用綁定的泛型參數(shù) T 去實例化Test 類,這個參數(shù)就必須同時實現(xiàn) Serializable 和 Cloneable。例如 String 不是,而 Date 是:
為了讓你有一個專門的交集類型,這種特性在Java8中得到了重用。如何用它呢?幾乎沒用。但是,當你在 lambda 表達式中強行應用這樣的類型時,就只有此種方法可行。
假設你在方法中使用了這種瘋狂的類型約束:
你需要一個實現(xiàn)了Runnable 和Serializable 的對象。為了讓你可以在某些地方執(zhí)行它,或是發(fā)送它,
Lambads 可以被序列化:
如果一個lambda 表達式的目標類型和所需參數(shù)是可序列化的,那么這個表達式就能序列化。
即使這是真的,那也不會自動的實現(xiàn)序列化標記接口,所以你必須自己動手。
現(xiàn)在你有一個可序列化的,但是它不能被執(zhí)行。
所以你必須自己加上:
英文:DZone,譯者:黑蔥
相關文章: