關於 Java 你不知道的十件事

2021-12-23 ImportNew

。(點擊上方公眾號,可快速關注)

編譯:ImportNew - 心靈是一棵開花的樹,

如有好文章投稿,請點擊 → 這裡了解詳情

作為Java 控,我們總是對不太可能直接使用,但能使我們更了解 Java 和 Java 虛擬機(Java Virtual Machine,JVM) 的晦澀細節感興趣。這也是我將 Lukas Eder 在 jooq.org 上寫的這篇文章發布出來的原因。

你在Java發布的時候就開始使用了嗎?還記得那時它叫「Oak」,面向對象也 (Object Oriented, OO )還是個熱門話題,C++ 程式設計師們覺得 Java 完全沒機會成功,Applet的出現也是一件新鮮大事?

我打賭下文中至少一半的內容你都不知道。讓我們來看看這些令人驚喜的 Java 細節吧。

1. 受檢異常(checked exception)這件事是不存在的

是這樣的,JVM 完全不知道這件事,都是Java語言做的[只有Java語言這麼幹]。

現在,異常檢查被公認為是個錯誤,正如 Brue 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 標籤。

更多詳情請參考這篇文章,

https://blog.jooq.org/2012/09/14/throw-checked-exceptions-like-runtime-exceptions-in-java/

或者 Stack Overflow 上的這篇文章。

http://stackoverflow.com/q/12580598/521799

2. 可以使用不同的返回值類型來重載方法

以下代碼是編譯不過的,對吧?

class Test {

    Object x() { return "abc"; }

    String x() { return "123"; }

}

是的,Java 不允許在一個類中通過不同的返回值類型和異常語句來重載方法。

不過稍等,Java 文檔中關於 Class.getMethod(String, Class…) 這樣寫道:

請注意,在一個類中會有多個匹配的方法,因為雖然 Java 語法規則禁止一個類中存在多個方法函數籤名相同僅僅返回類型不同,但 JVM 允許。這樣提高了 JVM 的靈活性以實現各種語言特性。例如,可以用橋接方法(bridge method)來實現方法的協變返回類型,橋接方法和被重載的方法可以有相同的函數籤名和不同的返回值類型。

喔,這是合理的。事實上,以下代碼就是這樣執行的,

abstract class Parent {

    abstract T x();

}

 

class Child extends Parent {

   @Override

    String x() { return "abc";}

}

Child 類編譯後的字節碼是這樣的:

// Method descriptor #15 ()Ljava/lang/String;

// Stack: 1, Locals: 1

java.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: 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]

看,T 在字節碼中就是 Object,這個很好理解。

合成橋接方法是編譯器自動生成的,因為 Parent.x() 籤名的返回值類型被認為是 Object。如果沒有這樣的橋接方法是無法在兼容二進位的前提下支持泛型的。因此,修改 JVM 是實現這個特性最簡單的方法了(同時實現了協變式覆蓋)。很聰明吧。

你明白語言的內部特性了嗎? 這裡有更多細節。

http://stackoverflow.com/q/442026/521799

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 Crazyy {}

 

class Test {

    @Crazyy int[][]  a1 = {{}};

    int @Crazyy [][] a2 = {{}};

    int[] @Crazyy [] a3 = {{}};

 

    @Crazyy int[] b1[]  = {{}};

    int @Crazyy [] b2[] = {{}};

    int[] b3 @Crazyy [] = {{}};

 

    @Crazyy int c1[][]  = {{}};

    int c2 @Crazyy [][] = {{}};

    int c3[] @Crazyy [] = {{}};

}

類型註解,它的詭異性只是被他強大的功能掩蓋了。

換句話說:

當我在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.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);

更多細節請看這裡。

https://blog.jooq.org/2013/10/08/java-auto-unboxing-gotcha-beware/

5. 你也不懂複合賦值運算符

很詭異嗎?讓我們來看以下兩段代碼:

i += j;

i = i + j;

直覺上,他們是等價的吧?事實上不是,Java 語言規範(Java Language Standard,JLS)中這樣寫道:

符合賦值表達式 E1 op= E2 與 E1 = (T)((E1) op (E2)) 是等價的,這裡 T 是 E1 的類型,期望 E1 只被求值一次。

很美吧,我想引用 Peter Lawrey 在 Stack Overflow 上回復,

http://stackoverflow.com/a/8710747/521799

這種類型轉換很好的一個例子是使用 *= or /=

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'

這個很有用吧?我會將它們應用到我的程序裡。原因你懂的。

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 緩存,然後使用自動裝箱(auto-boxing)和自動拆箱(auto-unboxing)。千萬不要這麼做,我們假設如果再做一次。

 https://blog.jooq.org/2013/10/17/add-some-entropy-to-your-jvm/ 

我在4周假期之前的最後一次代碼提交中這麼做了。

7. GOTO

這是我喜歡的一個。Java 有 GOTO 語句!輸入以下:

int goto = 1;

結果將會是:

Test.java:44: error: <identifier> expected

int goto = 1;

這是因為 goto 是一個保留的關鍵字,以防萬一……

但這不是最激動人心的部分。最給力的是你可以通過 break、continue 以及標籤代碼塊來實現 goto。

向前跳轉

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  ..

8. Java 支持類型別名(type aliases)

在其它語言中(例如:Ceylon),定義類型別名是很容易的。

interface People => Set<Person>;

People 類型通過這個方法就可以與 Set<Person> 互換使用了:

People?      p1 = null;

Set</Person><Person>? p2 = p1;

People?      p3 = p2;

在 Java 中,頂層代碼裡是不能定義類型別名的,但是我們可以在類和方法的作用域內這麼做。假設我們不喜歡 Integer,[、]Long 這些名字,想要短一點的如 I 和 L,這是小菜一碟:

class Test<I extends Integer> {

    <L extends Long> void x(I i, L l) {

        System.out.println(

            i.intValue() + ", " +

            l.longValue()

        );

    }

}

以上代碼中,Integer 在 Test 類中用別名 I 替換, Long 在 x() 方法中用別名 L 替換。我們可以這樣調用以上方法:

new Test().x(1, 2L);

這個技術別太當真。在上邊的例子裡,Integer 和 Long 都是 final 類型, 也就是說 I 和 L 效果上是類型別名(大多數情況下,賦值兼容是單向的)。如果我們用非 final 的類型(例如 Object),就需要使用原來的泛型了。

以上是一些雕蟲小技,下面才是真正有用的!

9. 一些類型之間的關係是不確定的!

這個會很有趣的,所以來一杯咖啡然後集中注意力。假設以下兩種類型:

// A helper type. You could also just use List

interface Type<T> {}

 

class C implements Type<Type <? super C>> {}

class D<P> implements Type<Type <? super D<D<P>>>> {}

類型 C 和 D 到底是什麼意思呢?

他們包含了遞歸,很像 java.lang.Enum ,但又稍有不同。考慮以下代碼:

public abstract class Enum<E extends Enum<E>> { ... }

以上定義中, enum 的實現是一個純粹的語法糖。

// This

enum MyEnum {}

 

// Is really just sugar for this

class MyEnum extends Enum<MyEnum> { ... }

記住這個,讓我們再回到剛才那兩個類型。下邊的代碼可以通過編譯嗎?

class Test {

    Type< ? super C> c = new C();

    Type< ? super D<Byte>> d = new D<Byte>();

}

這是個很難的問題,Ross Tate 已經回答了。答案是不確定的:

C 是 <? super C> 的子類型嗎?

Step 0) C <?: Type<? super C>

Step 1) Type<Type<? super C>> <?: Type (inheritance)

Step 2) C  (checking wildcard ? super C)

Step . . . (cycle forever)

然後

D 是 <? super D<Byte>> 的子類型嗎?

Step 0) D<Byte> <?: Type<? super C<Byte>>

Step 1) Type<Type<? super D<D<Byte>>>> <?: Type<? super D<Byte>>

Step 2) D<Byte> <?: Type<? super D<D<Byte>>>

Step 3) Type<Type<? super C<C>>> <?: Type<? super C<C>>

Step 4) D<D<Byte>> <?: Type<? super D<D<Byte>>>

Step . . . (expand forever)

嘗試在 Eclipse 中編譯以上代碼,Eclipse 會掛掉的!(不要擔心,我已經提過 bug 了)

理解下這個…

Java 中的一些類型的關係是不確定的!

如果你想了解更多關於 Java 的這個特性,請閱讀 Ross Tate 與 Alan Leung 和 Sorin Lerner 共同編著的論文 「Taming Wildcards in Java’s Type System」或者我們自己總結的 correlating subtype polymorphism with generic polymorphism。

《 Taming Wildcards in Java’s Type System 》

http://www.cs.cornell.edu/~ross/publications/tamewild/tamewild-tate-pldi11.pdf

《 correlating subtype polymorphism with generic polymorphism 》

https://blog.jooq.org/2013/06/28/the-dangers-of-correlating-subtype-polymorphism-with-generic-polymorphism/

10. 類型交集(Type intersections)

Java 有個特性叫做類型交集。你可以聲明一個泛型,這個泛型是兩個類型的交集,例如:

class Test<T extends Serializable & Cloneable> {

}

綁定到 Test 類的實例的泛型類型參數 T 需要同時實現 Serializable 和 Cloneable。例如,String 是不能綁定的,但 Date 可以:

// Doesn't compile 

Test<String> s = null;

 

// Compiles

Test<Date> d = null;

Java 8 中保留了這個功能,你可以將類型轉換為臨時的類型交集。這有用嗎?幾乎沒用,但如果你想要將lambda表達式強制轉換為這個類型,除此就別無他法了。我們假設你的方法有這個瘋狂的類型限制:

<T extends Runnable & Serializable> void execute(T t) {}

你想要同時支持 Runnable 和 Serializable,是為了以防萬一要在網絡的另一處執行它。Lambda 和序列化都有些古怪:

Lambda 表達式可以被序列化:

如果一個 lambda 表達式的返回值和輸入參數可以被序列化,則這個表達式是可以被序列化的。

但即使這是真的,它也不會自動繼承 Serializable 接口。你需要轉換才能成為那個類型。但如果你只是轉換為 Serializable…

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

lambda 就不支持 Runnable 了。

所以,

把它轉換為兩個類型:

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

結論

我經常只這麼說 SQL,但現在要用下邊的話來總結這篇文章了:

Java 語言的詭異性只是被它解決問題的能力掩蓋了。

看完本文有收穫?請轉發分享給更多人

關注「ImportNew」,提升Java技能

相關焦點

  • 關於Java你不知道的十件事
    還記得那些被稱為「 Oak」的日子,OO仍然是熱門話題,C ++人士認為Java沒有機會,Applet還是一件事嗎?我敢打賭,您至少不了解以下一半內容。1.沒有被檢查的異常那就對了!JVM不知道任何這樣的事情,只有Java語言知道。
  • 關於 Java 序列化你不知道的 5 件事
    Hashtable 和 HashMap 在磁碟上的格式是不相同、不兼容的。除非對每個持久化的用戶設置運行某種類型的數據轉換實用程序(極其龐大的任務),否則以後似乎只能一直用Hashtable 作為應用程式的存儲格式。團隊感到陷入僵局,但這只是因為他們不知道關於 Java 序列化的一個重要事實:Java 序列化允許隨著時間的推移而改變類型。
  • 關於 Java 對象序列化您不知道的 5 件事
    除非對每個持久化的用戶設置運行某種類型的數據轉換實用程序(極其龐大的任務),否則以後似乎只能一直用Hashtable 作為應用程式的存儲格式。團隊感到陷入僵局,但這只是因為他們不知道關於 Java 序列化的一個重要事實:Java 序列化允許隨著時間的推移而改變類型。當我向他們展示如何自動進行序列化替換後,他們終於按計劃完成了向 HashMap 的轉變。
  • 關於 Java 你不知道的 10 件事
    JVM 不會知道這些事情,只有 Java 語句知道。如今大家都認為檢查異常是個錯誤。正如 Bruce Eckel 在布拉格 GeeCON 閉幕時所說,Java 之後再沒別的語言檢查異常,甚至 Java 8 在新的 Stream API 中也不再幹這個事情(如果你的 Lambda 使用 IO 和 JDBC,這其實還是有點痛苦)。如何證實 JVM 並不清楚檢查異常一事?
  • 關於 Java 你必須知道的 5 件事!
    不知道努力方向」耶魯大學教授給研究生的忠告:沒有人能管你年薪50萬是什麼樣的工作?你是從很早開始就一直使用 Java 嗎?那你還記得它的過去嗎?那時,Java 還叫 Oak,OO 還是一個熱門話題,C++ 的 folk 者認為 Java 是不可能火起來,Java 開發的小應用程式 Applets 還受到關注。我敢打賭,下面我要介紹的這些事,有一半你都不知道。下面讓我們來深入探索 Java 的神秘之處。1.
  • 你需要知道的關於海洋垃圾的十件事
    你需要知道的關於海洋垃圾的十件事
  • 關於全畫幅微單相機 你可能不知道的十件事
    關於全畫幅微單相機,即便是專業用戶可能也不能夠全面了解,因此今天筆者就來給大家普及全畫幅微單你並不了解的十件事。關於全畫幅微單相機 你可能不知道的十件事一、全畫幅微單多為全新的系統    目前正在銷售的全畫幅微單相機的品牌是佳能、尼康、索尼、松下和徠卡五家
  • 關於《神探夏洛克》你不知道的十件事
    下面放鬆一下心情,一起來回顧一下關於《神夏》的十件趣事,好好期待第四季第二集播出吧,畢竟誰也不知道下一季又要等到什麼時候了。As Sherlock returns to our screens, here are 10 curious things you might not have known about the BBC series:BBC電視劇《神探夏洛克》回歸了,下面是關於這部劇你可能有所不知的
  • 關於愛因斯坦你不知道的十件事
    大多數人都知道, 愛因斯坦是一個著名科學家,他想出了著名公式e = mc2 。但是,有10件事也許你不知道。  Most people know that Albert Einstein was a famous scientist who came up with the formula E=mc2.
  • 關於高潮,你一定不知道的十件事
    中國人對於這一塊的事總是避而不談,所以我們對這塊的內容知之甚少。那麼關於它,有哪些為人所罕知的呢?它對於生物來說又有怎樣的意義呢?要是你用電極刺激這兒,你可能就會得到一場極致的快樂。並且刺激死人的時候也能夠引起脊椎反射。當然,這裡的死人指的是腦死亡但有心跳的人。如果你對一個這樣的人刺激他身體上的某些特定部位,你常常會看到他的手會自己動起來抱在胸前,這是一種叫拉薩路反射(Lazarus reflex)的現象。
  • 關於4K超高畫質電視 你必須知道的十件事
    14K電視的基礎知識知多少    【中關村在線電視頻道原創】目前在國外很多網站中,我們經常會看到入諸如「關於某某你必須知道的N件事」,類似於這樣的內容。   最近,國外有網站發布了關於4K電視你必須知道的十件事,對現在流行的4K超高畫質電視進行了探討。那麼,國外友人在面對4K電視方面,是否和我們的想法相同又有何不一樣的見解呢?
  • 漲姿勢:關於艾菲爾鐵塔你不知道的十件事
    此文為您揭曉艾菲爾鐵塔——巴黎,乃至全法國的心臟——的十件趣事。   1.The Eiffel Tower was originally painted red.   艾菲爾鐵塔最初被漆成紅色。   It appeared in red color in the center of Paris in 1889.
  • 關於「套套」你所不知道的9件事
    然而,關於戴套你不知道的事可能改變你的人生。當你以正確的方式戴套套的時候,出事的機率是非常低的。但很不幸,這種理想情況通常只出現在實驗室的條件下。當談到以正確的方法戴套套的時候,你所不知道的東西會傷害你。(如果需要背景閱讀,可以參考安全性愛終極指南。)你所不知道的首先,我們來聊聊關於套套的幾個謬見—這樣的謬見有許多—能妨礙你堅持使用套套。要知道,合理使用套套的第一步是在愛愛之前戴上它。謬見:男人戴上套套就不會有快感。
  • 關於跑鞋你不知道的20件事
    [導讀]  以下是你可能不知道的關於跑鞋的20件事,看看你都知道多少:  1、跑鞋面料的創新技術來源於女性內衣——彈性、柔軟、貼身、支持;  2、跑者該穿中性、穩定性和運動控制兼備的跑鞋,而不是隨便都行。  3、耐克取名自希臘勝利女神,也是40s50s的防空飛彈系統。其前身是鬼冢虎的美國分銷公司。
  • 面對「扣肉」CPU 你必須知道的十件事
    面對「扣肉」CPU 你必須知道的十件事 2006年07月13日 05:17作者:靳勝春編輯:靳勝春   這個月底,備受期待的Conroe處理器即將發布
  • 關於增強Swing您所不知道的5件事
    在本期的 5 件事 系列 文章中,我將向您介紹 4 個免費開源組件,您可以用來現代化您的 Swing GUI,並圍繞那些您可能還不可了解的 Swing 線程展開討論。  1. Substance  將一個 Java 應用程式和一個本地作業系統整合起來是比較麻煩的,主要是因為 Swing 的組件是人工繪製的。
  • 關於魅族Pro 7背部畫屏 這十件事你必須知道!
    原標題:關於魅族Pro 7畫屏 這十件事你必須知道 「雙瞳如小窗 佳景觀歷歷」 ,一首楊柘大師風格的詩句讓魅族Pro 7的品牌格調提升到了新的文學高度,與此同時魅族Pro 7 Plus的售價也達到了4080元的新高,邁出了魅族品牌升級的第一步。不少網友戲稱,魅族Pro 7背部這塊1.9英寸的畫屏至少值1000塊。
  • 關於中國與寮國的關係,你需要知道的幾件事
    原標題:新聞背景:關於中老關係,你需要知道的幾件事應寮國人民革命黨中央委員會總書記、寮人民民主共和國主席本揚邀請,中共中央總書記、國家主席習近平將對寮國進行國事訪問。當前,中老關係正處在歷史最好時期。關於兩國關係,你需要知道以下幾件事。寮國北接中國,兩國山水相連,友好交往源遠流長。
  • 租借嫁衣你必須知道的十件事
    可是,有時好事多磨,由於新娘在婚禮上太過激動或是忙於接待親友,致使妝容、造型出現小狀況,這時,新娘們該如何應付這些突發情況,臨陣不亂呢?租借嫁衣必須知道十件事技巧一 價格租婚紗的價格,根據婚紗的材質和款式有所不同。
  • 關於肯德基你不知道的10件事
    至少,你可以認出上校和他那著名的紅色炸雞桶。最重要的是,你可能已經讀過無數關於這個系列的新聞故事——好的和壞的,正確的和錯誤的。但這裡有一些你可能沒有讀過或聽說過的關於這個快餐業巨頭的事情,會讓你大吃一驚。