JavaRush /Java Blog /Random-TW /關於 Java 你不知道的 10 件事
minuteman
等級 32

關於 Java 你不知道的 10 件事

在 Random-TW 群組發布
那麼,您最近開始使用 Java 了嗎?還記得它被稱為「Oak」的日子嗎?那時,物件導向仍然是一個熱門話題,那時,C++ 人們認為 Java 沒有機會,甚至沒有人聽說過 Applet。我可以假設你連以下事情的一半都不知道。讓我們以一些有關 Java 內部工作原理的酷炫驚喜開始新的一周。關於 Java 你不知道的 10 件事 - 11. 不存在受檢查異常這樣的東西。 這是正確的!JVM 不知道這樣的事情,只有 Java 語言知道。今天,每個人都同意檢查異常是一個錯誤。正如Bruce Eckel 在布拉格GeeCON 的最後一次演講中所說,自Java 以來,沒有其他語言使用檢查異常,甚至Java 8 也不再在新的Streams API 中涵蓋它們(當您的lambda 使用IO 或JDBC 時,這可能會帶來一些不便)。你想證明 JVM 不知道這樣的事情嗎?嘗試以下程式碼: 這不僅會編譯,還會拋出 SQLException,您甚至不需要為此使用 Lombok 的 @SneakyThrows。 2. 您可以擁有僅返回類型不同的重載方法public class Test { // No throws clause here public static void main(String[] args) { doThrow(new SQLException()); } static void doThrow(Exception e) { Test. doThrow0(e); } @SuppressWarnings("unchecked") static void doThrow0(Exception e) throws E { throw (E) e; } } 這不會編譯,對嗎? class Test { Object x() { return "abc"; } String x() { return "123"; } } 正確的。Java 語言不允許在同一個類別中同時等效地重寫兩個方法,無論它們的拋出或傳回類型有何不同。但等一下。再次檢查 Class.getMethod(String, Class...) 的文件。它說: 請注意,一個類別中可能有多個相應的方法,因為雖然Java語言禁止多個具有相同簽章但傳回類型不同的方法,但Java虛擬機卻不這樣做。虛擬機器的這種靈活性可用於實現各種語言功能。例如,協變返回可以透過橋接方法來完成;橋接方法和重寫方法將具有相同的簽名,但返回類型不同。 哇,這很有道理。當您編寫以下內容時,實際上發生了很多事情: 查看生成的字節碼: 所以 t 實際上是字節碼中的一個物件。這是很好理解的。合成橋方法實際上是由編譯器產生的,因為在呼叫的某些部分可以預期 Parent.x() 的回傳類型。在二進位表示中將不再可能添加沒有此類橋接方法的泛型。因此,更改 JVM 以允許這樣的功能產生更少的痛苦(這也允許協變方法重寫作為副作用...)聰明吧? 3. 以下均為二維數組。 這實際上是真的。即使您的心理分析器無法立即理解上述方法的返回類型,它們都是相同的!就像下一段程式碼一樣。 你認為這很瘋狂嗎?寫作的機會之多也簡直爆炸想像力! 鍵入註解。 這種裝置的神秘性僅次於它的力量。 或者換句話說:當我在 4 週假期之前做出最後一次承諾時。 我允許您以任何您喜歡的方式使用它。 4. 你不會得到條件語句 那麼,當你開始使用條件語句時,你認為你已經了解條件語句的所有內容了嗎?讓我讓你失望吧——你錯了。大多數人都會認為下面兩個例子是等價的: 等價於這個? 不。讓我們快速測試一下 程式將輸出以下內容: 是的!如果需要,條件運算子將執行類型轉換。因為否則你會期望程式拋出 NullPointerException? 5. 您也不會得到複合賦值運算子。 足智多謀就夠了嗎?我們來看下面兩個程式碼片段: abstract class Parent { abstract T x(); } class Child extends Parent { @Override String x() { return "abc"; } } // Method descriptor #15 ()Ljava/lang/String; // Stack: 1, Locals: 1 java.lang.String x(); 0 ldc [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: 1 bridge 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] 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[][] = {{}}; } @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 [] = {{}}; } 關於 Java 你不知道的 10 件事 - 2 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.0 1 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); i += j; i = i + j; 直觀上來說,它們應該是相等的,對吧?但你知道嗎——它們是不同的。JLS 規範規定: E1 op = E2 類型的複合表達式等效於 E1 = (T) ((E1) op (E2)),其中 T 是 E1 的類型,但 E1 僅計算一次。 一個很好的例子是使用 *= 或 /= : byte b = 10; b *= 5.7; System.out.println(b); // prints 57 or: byte b = 100; b /= 2.5; System.out.println(b); // prints 40 or: char ch = '0'; ch *= 1.1; System.out.println(ch); // prints '4' or: char ch = 'A'; ch *= 1.5; System.out.println(ch); // prints 'a' 那麼,這仍然是一個有用的工具嗎? 6. 隨機整數 現在來做一個更困難的任務。不要閱讀解決方案。看看你自己是否能找到答案。當我運行以下程式時: for (int i = 0; i < 10; i++) { System.out.println((Integer) i); } 有時我會得到以下輸出: 92 221 45 48 236 183 39 193 33 84 但這怎麼可能呢?好吧,答案在於透過反射來覆蓋 JDK 的 Integer 緩存,然後使用自動裝箱和自動拆箱。未經成人許可,請勿這樣做!或者換句話說: 關於 Java 你不知道的 10 件事 - 3 7. GOTO 我的最愛之一。Java 有 GOTO!寫這篇: int goto = 1; 你會得到這個: 那是因為 goto 是一個未使用的保留字,以防萬一......但這不是令人興奮的部分。最酷的是,你可以包含 goto 與break、continue和標記區塊配對: 在字節碼中向前跳: 在字節碼中 向後跳轉 : 8. Java有類型別名 在其他語言(如Ceylon )中,我們可以定義型別別名非常簡單: 這裡的 People 類別的建構方式可以與 Set 互換 Test.java:44: error: expected int goto = 1; ^ label: { // do stuff if (check) break label; // do more stuff } 2 iload_1 [check] 3 ifeq 6 // Jumping forward 6 .. label: do { // do stuff if (check) continue label; // do more stuff break label; } while(true); 2 iload_1 [check] 3 ifeq 9 6 goto 2 // Jumping backward 9 .. interface People => Set ; : 在Java中,我們不能簡單地在頂層定義別名。但我們可以根據類別或方法的需要來這樣做。假設我們對 Integer、Long 等名稱不滿意。我們想要更短的名稱:I 和 L。簡單: 在上面的範例中,為了 Test 類別的可見性,Integer 被轉換為 I,而 Long 被轉換為 L 以滿足 x() 方法的需要。 現在我們可以這樣呼叫這個方法: 當然,這個技術不應該被認真看待。 在這種情況下,Integer 和 Long 是最終類型,這意味著 I 和 L 是有效的轉換(幾乎,轉換只進行單向)。 如果我們決定使用非最終類型(例如 Object),那麼我們可以使用常規泛型。 我們玩了一點就夠了。 讓我們繼續討論一些真正有趣的事情。 9. 有些類型關係是不可判定的! 好吧,現在這會變得非常有趣,所以拿起一杯濃縮咖啡,讓我們看看以下兩種類型: People? p1 = null; Set ? p2 = p1; People? p3 = p2; class Test { void x(I i, L l) { System.out.println( i.intValue() + ", " + l.longValue() ); } } new Test().x(1, 2L);// A helper type. You could also just use List interface Type {} class C implements Type > {} class D

implements Type >>> {} 那麼C和D到底是什麼意思呢?從某種意義上說,它們是遞歸的,類似於 java.lang.Enum 中的遞歸。考慮到: 考慮到上面的規範,枚舉的實際實現只是語法糖: 考慮到這一點,讓我們回到我們的兩種類型。下面的程式碼能編譯通過嗎? 一個難題……而且實際上還沒有解決? C 是 Type 的子類型嗎 嘗試在您的 Eclipse 或 Idea 中編譯它,他們會告訴您他們的想法。把它衝進下水道… Java中的一些型別關係是不可判定的!10. 類型交集 Java 有一個非常有趣的特性,稱為型別交集。您可以聲明一個實際上是兩種類型的交集的(通用)類型。例如: 與 Test 類別的實例關聯的自訂類型參數 T 必須同時包含 Serialized 和 Cloneable 介面。例如,String 不能受到限制,但 Date 可以: 此功能在 Java8 中有多種用途,您可以在其中強制轉換類型。這有什麼幫助?幾乎什麼都沒有,但如果你想將 lambda 表達式轉換為你需要的類型,那麼沒有其他方法。假設您的方法有一個瘋狂的限制: 只有當您想在另一個地方執行它並透過網路發送結果時,您才需要 Runnable ,同時它也是可序列化的。Lambda 和序列化增添了一點諷刺意味。 如果 lambda 表達式的目標類型和參數是可序列化的,則可以序列化它。 但即使這是真的,它們也不會自動啟用 Serialized 介面。你必須自己把它們變成這種類型。但是,當您僅強制轉換為 Serialized: 時,lambda 將不再是 Runnable,因此將它們強制轉換為兩種類型: 總而言之: public abstract class Enum > { ... } // This enum MyEnum {} // Is really just sugar for this class MyEnum extends Enum { ... } class Test { Type c = new C(); Type> d = new D (); } Step 0) C Step 1) Type > >? Step 0) D > Step 1) Type >>> > Step 2) D >> Step 3) List >> > Step 4) D > >> Step . . . (expand forever) class Test { } // Doesn't compile Test s = null; // Compiles Test d = null; void execute(T t) {} execute((Serializable) (() -> {}));execute((Runnable & Serializable) (() -> {}));

Java 既強大又神秘。

留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION