當前位置:
首頁 > 知識 > 關於 Java 你不知道的 10 件事

關於 Java 你不知道的 10 件事

作者: Lukas Eder


原文:10 Things You Didn』t Know About Java


來源:oschina


譯文:www.oschina.net/translate/10-things-you-didnt-know-about-java


作為 Java 書獃子,比起實用技能,我們會對介紹 Java 和 JVM 的概念細節更感興趣。因此我想推薦 Lukas Eder 在 jooq.org 發表的原創作品給大家。

你是從很早開始就一直使用 Java 嗎?那你還記得它的過去嗎?那時,Java 還叫 Oak,OO 還是一個熱門話題,C++ 的 folk 者認為 Java 是不可能火起來,Java 開發的小應用程序 Applets 還受到關注。


我敢打賭,下面我要介紹的這些事,有一半你都不知道。下面讓我們來深入探索 Java 的神秘之處。


1


沒有檢查異常這種事情


沒錯!JVM 不會知道這些事情,只有 Java 語句知道。


如今大家都認為檢查異常是個錯誤。正如 Bruce Eckel 在布拉格 GeeCON 閉幕時所說,Java 之後再沒別的語言檢查異常,甚至 Java 8 在新的 Stream API 中也不再干這個事情(如果你的 Lambda 使用 IO 和 JDBC,這其實還是有點痛苦)。


如何證實 JVM 並不清楚檢查異常一事?試試下面的代碼:


publicclassTest{// No throws clause herepublicstaticvoidmain(String[] args){ doThrow(newSQLException()); }staticvoiddoThrow(Exception e){ Test. doThrow0(e); }@SuppressWarnings("unchecked")staticvoiddoThrow0(Exception e)throwsE{throw(E) e; }}


這不僅可以編譯通過,它還可以拋出 SQLException。你甚至不需要 Lombok 的 @SneakyThrows 就能辦到。


這篇文章可以看到更詳細的相關內容,或者在 Stack Overflow 上看。

2


你可以定義僅在返回值有差異的重載函數


這樣的代碼無法編譯,對不?


classTest{Objectx(){return"abc"; }Stringx(){return"123"; }}


對。 Java 語言不允許兩個方法在同一個類中「等效重載」,而忽略其諸如throws自居或返回類型等的潛在的差異。


查看 Class.getMethod(String, Class…) 的 Javadoc。 其中說明如下:


請注意,類中可能有多個匹配方法,因為 Java 語言禁止在一個類聲明具有相同簽名但返回類型不同的多個方法,但 Java 虛擬機並不是如此。虛擬機中增加的靈活性可以用於實現各種語言特徵。例如,可以用橋接方法實現協變參返回; 橋接方法和被重寫的方法將具有相同的簽名但擁有不同的返回類型。


哇哦,有道理。實際上下面的代碼暗藏著很多事情:


abstractclassParent{abstractTx();}classChildextendsParent{@OverrideStringx(){return"abc"; }}


來看看為 Child 生成的位元組碼:

// Method descriptor #15 ()Ljava/lang/String;// Stack: 1, Locals: 1java.lang.Stringx();ldc [16]2areturn Line numbers: [pc:, line:7] Local variable table: [pc:, pc:3] local:thisindex:type: Child// Method descriptor #18 ()Ljava/lang/Object;// Stack: 1, Locals: 1bridge synthetic java.lang.Objectx();aload_0 [this]1invokevirtual Child.x() : java.lang.String [19]4areturn Line numbers: [pc:, line:1]


其實在位元組碼中 T 真的只是 Object。這很好理解。


合成的橋方法實際是由編譯器生成的,因為 Parent.x() 簽名中的返回類型在實際調用的時候正好是 Object。在沒有這種橋方法的情況下引入泛型將無法在二進位下兼容。因此,改變 JVM 來允許這個特性所帶來的痛苦會更小(副作用是允許協變凌駕於一切之上) 很聰明,不是嗎?


你看過語言內部的細節嗎?不妨看看,在這裡會發現更多很有意思的東西。


3


所有這些都是二維數組!


classTest{int[][] a() {returnnewint[][]; }int[] b() [] {returnnewint[][]; }intc()[][]{returnnewint[][]; }}


是的,這是真的。即使你的大腦解析器不能立刻理解上面方法的返回類型,但其實他們都是一樣的!類似的還有下面這些代碼片段:


classTest{int[][] a = {{}};int[] b[] = {{}};intc[][] = {{}};}


你認為這很瘋狂?想像在上面使用 JSR-308 / Java 8 類型註解 。語法的可能性指數激增!

@Target(ElementType.TYPE_USE)@interfaceCrazy {}classTest{@Crazyint[][] a1 = {{}};int@Crazy[][] a2 = {{}};int[]@Crazy[] a3 = {{}};@Crazyint[] b1[] = {{}};int@Crazy[] b2[] = {{}};int[] b3@Crazy[] = {{}};@Crazyintc1[][] = {{}};intc2@Crazy[][] = {{}};intc3[]@Crazy[] = {{}};}


類型註解。看起來很神秘,其實並不難理解。


或者換句話說:


當我做最近一次提交的時候是在我4周的假期之前。

關於 Java 你不知道的 10 件事



對你來說,上面的內容在你的實際使用中找到了吧。


4


條件表達式的特殊情況


可能大多數人會認為:

Object o1 =true?newInteger(1) :newDouble(2.0);


是否等價於:


Object o2;if(true) o2 =newInteger(1);elseo2 =newDouble(2.0);


然而,事實並非如此。我們來測試一下就知道了。


輸出結果:


1.0


1


由此可見,三目條件運算符會在有需要的情況下,對操作數進行類型提升。注意,是只在有需要時才進行;否則,代碼可能會拋出 NullPointerException 空引用異常:


Integer i =newInteger(1);if(i.equals(1)) i =null;Double d =newDouble(2.0);Object o =true? i : d;// NullPointerException!System.out.println(o);


5

你還沒搞懂複合賦值運算符


很奇怪嗎?來看看下面這兩行代碼:


i += j;i = i + j;


直觀看來它們等價,是嗎?但可其實它們並不等價!JLS 解釋如下:


E1 op= E2 形式的複合賦值表達式等價於 E1 = (T)((E1) op (E2)),這裡 T 是 E1 的類型,E1 只計算一次。


非常好,我想引用 Peter Lawrey Stack Overflow 上的對這個問題的回答:


使用 *= 或 /= 來進行計算的例子


byteb =10;b *=5.7;System.out.println(b);// prints 57


或者


byteb =100;b /=2.5;System.out.println(b);// prints 40

或者


charch = 0 ;ch *=1.1;System.out.println(ch);// prints 4


或者


charch = A ;ch *=1.5;System.out.println(ch);// prints a


現在看到它的作用了嗎?我會在應用程序中對字元串進行乘法計算。因為,你懂的…...


6


隨機整數


現在有一個更難的謎題。不要去看答案,看看你能不能自己找到答案。如果運行下面的程序:


… 「有時候」,我會得到下面的輸出:


92

221


45


48


236


183


39


193


33


84


這怎麼可能??

. spoiler… 繼續解答…


好了,答案在這裡 (https://blog.jooq.org/2013/10/17/add-some-entropy-to-your-jvm/),這必須通過反射重寫 JDK 的 Integer 緩存,然後使用自動裝箱和拆箱。不要在家幹這種事情!或者,我們應該換種方式進行此類操作。

關於 Java 你不知道的 10 件事



7


GOTO


這是我的最愛之一。Java也有GOTO!輸入下試試……


intgoto =1;


將輸出:


Test.java:44: error: expectedintgoto =1;^


這是因為goto是一個未使用的關鍵字, 僅僅是為了以防萬一……


但這不是最令人興奮的部分。令人興奮的部分是你可以使用 break、continue 和標記塊來實現 goto 功能:


向前跳:


label: {// do stuffif(check)breaklabel;// do more stuff}


在位元組碼中格式如下:


2iload_1 [check]3ifeq6// Jumping forward6..


向後跳:


label: do {// do stuffif(check)continuelabel;// do more stuffbreaklabel;}while(true);


在位元組碼中格式如下:


2iload_1 [check]3ifeq96goto2// Jumping backward9..

關於 Java 你不知道的 10 件事



8


Java 有類型別名


其它語言 (比如 Ceylon) 中,我們很容易為類型定義別名:


interfacePeople=> Set


;


這裡產生了 People 類型,使用它就跟使用 Set


一樣:


People? p1 =null;Set


? p2 = p1;People? p3 = p2;


Java 中我們不能在頂層作用域定義類型別名,但是我們可以在類或方法作用域中干這個事情。假如我們不喜歡 Integer、Long 等等名稱,而是想用更簡短的 I 和 L,很簡單:


在上面的程序中,Test 類作用域內 Integer 被賦予 I 這樣的 「別名」,類似地,Long 在 x() 方法中被賦予 L 這樣的 「別名」。之後我們可以這樣調用方法:


newTest().x(1,2L);


這種技術當然不太會受重視。這種情況下,Integer 和 Long 都是 final 類型,也就是說,I 和 L 是事實上的別名(基本上賦值兼容性只需要考慮一種可能性)。如果我們使用非 final 類型 (比如 Object),那就是一般的泛型。


這些把戲已經玩夠了。現在來看看真正了不起的東西!


9


某些類型的關係並不確定!


好了,這會很引人注目,先來杯咖啡提提神。思考一下下面兩個類型:


// A helper type. You could also just use ListinterfaceType{}classCimplementsType


現在告訴我,類型 C 和 D 到底是什麼?


它們存在遞歸,是一種類似 java.lang.Enum (但有略微不同)的遞歸方式。看看:


publicabstractclassEnum{ ... }


在上面的描述中,enum 實際上只是單純的語法糖:


// ThisenumMyEnum {}// Is really just sugar for thisclassMyEnumextendsEnum{ ... }


認識到這一點之後我們回過頭來看看前面提到的兩個類型,下面的代碼會編譯成什麼樣?


classTest{ Type< ?superC> c =newC(); Type< ?superD> d =newD();}


非常難回答的問題,不過 Ross Tate 已經回答了。這個問題的答案是不可判定的:


C 是 Type


Step) C


然後:


D 是 Type


Step) D


Step . . . (expand forever)


在 Eclipse 中試著編譯一下,它會崩潰! (不用擔心,我提交了 BUG 報告)


讓這個事情沉下去…


Java 中某些類型的關係是不明確的!


如果你對 Java 這個用法感到奇怪之餘也感興趣,就去看看 Ross Tate 寫的 「在 Java 的類型系統中使用通配符」 (與 Alan Leung 和 Sorin Lerner 合著),我們也在討論泛型多態中的相關子類多態性。


10


類型交集


Java 有一個非常奇怪的特性叫類型交集。你可以申明某個(泛型)類型,而它實際上是兩個類型的交集,比如:


classTest{


}


綁定到 Test 類型實例的泛型類型參數 T 必須實現 Serializable 和 Cloneable。比如,String 就不符合要求,但 Dete 滿足:


// Doesn t compileTest s =null;// CompilesTest d =null;


這個特性已經在 Java 8 中使用。這很有用嗎?幾乎沒用,但是如果你希望某個 Lambda 表達式是這種類型,還真沒別的辦法。假設你的方法有這種瘋狂的類型約束:


voidexecute(T t){}


你想通過執行它得到一個可以序列化 (Serializable) 的 Runnable 對象。Lambda 和序列化也有點奇怪。


Lambda 可以序列經:


如果 Lambda 的目標類型和參數類型都可以序列化,那麼你可以序列化這個 Lambda


但是即使是這樣,他們都不能自動實現 Serializable 標記介面。你必須強制轉換類型。但是當你只扔給 Serializable 時…...


execute((Serializable) (() -> {}));


… 那麼 lambda 將不再是 Runnable 的。


因此要把它轉換為兩種類型:


execute((Runnable & Serializable) (() -> {}));


結論


一句話總結這篇文章就是:


Java 恰好是一種看起來神秘的語言,其實不然。


微信公眾號內回複數字「1」


小編拉你進粉絲微信群


不是在文章評論里回復


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 程序員之家 的精彩文章:

每個程序猿應該閱讀的10本經典書籍
揭開程序員裝 13 行為的面具
C#創建壓縮文件
你是一個努力工作的程序員嗎?
十大演算法,讓你輕鬆進階高手

TAG:程序員之家 |

您可能感興趣

關於 Palace 你也許不知的 7 件事
關於 Supreme 你需要了解的33件事
關於Facebook廣告推廣 你該知道的11件事
關於Harden Vol.2 你需要知道這些
拍攝MV竟然被捕!關於Teka$hi 6ix9ine 你需要了解的10個事實!
關於Sweet fitness你不知道的事
你所不知道的關於Tepito的7個「秘密」
你或許不知道關於C羅的33件事
關於madness×converse 那些你不知道的小事
關於15/15plus,你需要知道的是
96%的人不知道的關於Google搜索的15個技巧
關於G-Eazy 你需要了解的10個事件!
關於新版9.7寸iPad,Apple沒有說的三件事
關於潮牌,你不應該只知道Supreme!
關於Amazon Go,你還想知道什麼?
關於隆多,你不知道的10件事
關於AJ1你必須知道的10間事
關於塵緣,你不知道的100件事
關於Camosun College,你知道多少?
關於三星Galaxy S9/S9+你想知道的都在這裡!