熱文導讀 | 點擊標題閱讀
寒門學子:進得去的名校,抹不掉的階層烙印
現在的大學生「大學越上越迷茫 不知道努力方向」
耶魯大學教授給研究生的忠告:沒有人能管你
年薪50萬是什麼樣的工作?
作為Java書呆子,比起實用技能,我們會對介紹Java和JVM的概念細節更感興趣。因此我想推薦LukasEder在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 並不清楚檢查異常一事?試試下面的代碼:
public class Test { // No throws clause here public static void main(String[] args) { doThrow(new SQLException()); } static void doThrow(Exception e) { Test.<RuntimeException> doThrow0(e); } @SuppressWarnings("unchecked") static <E extends Exception> void doThrow0(Exception e) throws E { throw (E) e; }}
這不僅可以編譯通過,它還可以拋出 SQLException。你甚至不需要 Lombok 的 @SneakyThrows 就能辦到。
這篇文章可以看到更詳細的相關內容,或者在 Stack Overflow 上看。
2. 你可以定義僅在返回值有差異的重載函數這樣的代碼無法編譯,對不?
class Test { Object x() { return "abc"; } String x() { return "123"; }}
對。 Java 語言不允許兩個方法在同一個類中「等效重載」,而忽略其諸如throws自居或返回類型等的潛在的差異。
查看 Class.getMethod(String, Class…) 的 Javadoc。 其中說明如下:
請注意,類中可能有多個匹配方法,因為 Java 語言禁止在一個類聲明具有相同籤名但返回類型不同的多個方法,但 Java 虛擬機並不是如此。虛擬機中增加的靈活性可以用於實現各種語言特徵。例如,可以用橋接方法實現協變參返回; 橋接方法和被重寫的方法將具有相同的籤名但擁有不同的返回類型。
哇哦,有道理。實際上下面的代碼暗藏著很多事情:
abstract class Parent<T> { abstract T x();}class Child extends Parent<String> { @Override String x() { return "abc"; }}
來看看為 Child 生成的字節碼:
// Method descriptor #15 ()Ljava/lang/String;// Stack: 1, Locals: 1java.lang.String x(); 0 ldc </String><String "abc"> [16] 2 areturn Line numbers: [pc: 0, line: 7] Local variable table: [pc: 0, pc: 3] local: this index: 0 type: Child// Method descriptor #18 ()Ljava/lang/Object;// Stack: 1, Locals: 1bridge synthetic java.lang.Object x(); 0 aload_0 [this] 1 invokevirtual Child.x() : java.lang.String [19] 4 areturn Line numbers: [pc: 0, line: 1]
其實在字節碼中 T 真的只是 Object。這很好理解。
合成的橋方法實際是由編譯器生成的,因為 Parent.x() 籤名中的返回類型在實際調用的時候正好是 Object。在沒有這種橋方法的情況下引入泛型將無法在二進位下兼容。因此,改變 JVM 來允許這個特性所帶來的痛苦會更小(副作用是允許協變凌駕於一切之上) 很聰明,不是嗎?
你看過語言內部的細節嗎?不妨看看,在這裡會發現更多很有意思的東西。
3. 所有這些都是二維數組!class Test { int[][] a() { return new int[0][]; } int[] b() [] { return new int[0][]; } int c() [][] { return new int[0][]; }}
是的,這是真的。即使你的大腦解析器不能立刻理解上面方法的返回類型,但其實他們都是一樣的!類似的還有下面這些代碼片段:
class Test { int[][] a = {{}}; int[] b[] = {{}}; int c[][] = {{}};}
你認為這很瘋狂?想像在上面使用 JSR-308 / Java 8 類型註解 。語法的可能性指數激增!
@Target(ElementType.TYPE_USE)@interface Crazy {}class Test { @Crazy int[][] a1 = {{}}; int @Crazy [][] a2 = {{}}; int[] @Crazy [] a3 = {{}}; @Crazy int[] b1[] = {{}}; int @Crazy [] b2[] = {{}}; int[] b3 @Crazy [] = {{}}; @Crazy int c1[][] = {{}}; int c2 @Crazy [][] = {{}}; int c3[] @Crazy [] = {{}};}
類型註解。看起來很神秘,其實並不難理解。
或者換句話說:
當我做最近一次提交的時候是在我4周的假期之前。
對你來說,上面的內容在你的實際使用中找到了吧。
4. 條件表達式的特殊情況可能大多數人會認為:
Object o1 = true ? new Integer(1) : new Double(2.0);
是否等價於:
Object o2;if (true) o2 = new Integer(1);else o2 = new Double(2.0);
然而,事實並非如此。我們來測試一下就知道了。
System.out.println(o1);System.out.println(o2);
輸出結果:
1.01
由此可見,三目條件運算符會在有需要的情況下,對操作數進行類型提升。注意,是只在有需要時才進行;否則,代碼可能會拋出 NullPointerException 空引用異常:
Integer i = new Integer(1);if (i.equals(1)) i = null;Double d = new Double(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 上的對這個問題的回答:
使用 *= 或 /= 來進行計算的例子
byte b = 10;b *= 5.7;System.out.println(b); // prints 57
或者
byte b = 100;b /= 2.5;System.out.println(b); // prints 40
或者
char ch = '0';ch *= 1.1;System.out.println(ch); // prints '4'
或者
char ch = 'A';ch *= 1.5;System.out.println(ch); // prints 'a'
現在看到它的作用了嗎?我會在應用程式中對字符串進行乘法計算。因為,你懂的…
結論一句話總結這篇文章就是:
Java 恰好是一種看起來神秘的語言,其實不然。
* 文章轉載自:https://www.oschina.net/translate/10-things-you-didnt-know-about-java
作者 | 圖文來自網絡、如涉及版權問題,請聯繫我們以便處理。文章內容純屬作者個人觀點,不代表本網觀點。
編輯 | 老貓
讀書吧 | QQ群:481160039