關(guān)于 Java 你不知道的十件事

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

你在Java發(fā)布的時(shí)候就開始使用了嗎?還記得那時(shí)它叫“Oak”,面向?qū)ο笠?(Object Oriented, OO )還是個(gè)熱門話題,C++ 程序員們覺得 Java 完全沒機(jī)會(huì)成功,Applet的出現(xiàn)也是一件新鮮大事?

我打賭下文中至少一半的內(nèi)容你都不知道。讓我們來看看這些令人驚喜的 Java 細(xì)節(jié)吧。

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

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

現(xiàn)在,異常檢查被公認(rèn)為是個(gè)錯(cuò)誤,正如 Brue Eckel 在布拉格的 GeeCON 大會(huì)上的閉幕詞中所說, Java 后的其他語言都不再使用異常檢查了,就連 Java 8 都不愿在新的 Stream API 中使用它了(當(dāng)你在 lambda 表達(dá)式中使用 IO 或者 JDBC 時(shí),是很痛苦的)。

你想要證明 JVM 不知道異常檢查這件事嗎?嘗試以下代碼:

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;

}

}

這個(gè)不僅會(huì)編譯,還會(huì)拋出 SQLException ,你甚至不需要 Lombok 的 @SneakyThrows 標(biāo)簽。

更多詳情請(qǐng)參考這篇文章,

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

或者 Stack Overflow 上的這篇文章。

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

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

以下代碼是編譯不過的,對(duì)吧?

class Test {

Object x() { return "abc"; }

String x() { return "123"; }

}

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

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

請(qǐng)注意,在一個(gè)類中會(huì)有多個(gè)匹配的方法,因?yàn)殡m然 Java 語法規(guī)則禁止一個(gè)類中存在多個(gè)方法函數(shù)簽名相同僅僅返回類型不同,但 JVM 允許。這樣提高了 JVM 的靈活性以實(shí)現(xiàn)各種語言特性。例如,可以用橋接方法(bridge method)來實(shí)現(xiàn)方法的協(xié)變返回類型,橋接方法和被重載的方法可以有相同的函數(shù)簽名和不同的返回值類型。

喔,這是合理的。事實(shí)上,以下代碼就是這樣執(zhí)行的,

abstract class Parent {

abstract T x();

}

class Child extends Parent {

@Override

String x() { return "abc";}

}

Child 類編譯后的字節(jié)碼是這樣的:

// 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]

看,T 在字節(jié)碼中就是 Object,這個(gè)很好理解。

合成橋接方法是編譯器自動(dòng)生成的,因?yàn)?Parent.x() 簽名的返回值類型被認(rèn)為是 Object。如果沒有這樣的橋接方法是無法在兼容二進(jìn)制的前提下支持泛型的。因此,修改 JVM 是實(shí)現(xiàn)這個(gè)特性最簡(jiǎn)單的方法了(同時(shí)實(shí)現(xiàn)了協(xié)變式覆蓋)。很聰明吧。

你明白語言的內(nèi)部特性了嗎?這里有更多細(xì)節(jié)。

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

3. 這些都是二維數(shù)組

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[][] = {{}};

}

你認(rèn)為很瘋狂是不是?如果使用 JSR-308 / Java 8 類型注解的話,語句的數(shù)量會(huì)爆炸性增長(zhǎng)的!

@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 [] = {{}};

}

類型注解,它的詭異性只是被他強(qiáng)大的功能掩蓋了。

換句話說:

當(dāng)我在4周假期之前的最后一次代碼提交中這么做的話

為以上所有內(nèi)容找到相應(yīng)的實(shí)際用例的任務(wù)就交給你啦。

4. 你不懂條件表達(dá)式

你以為你已經(jīng)很了解條件表達(dá)式了嗎?我告訴你,不是的。大多數(shù)人會(huì)認(rèn)為以下的兩個(gè)代碼片段是等效的:

Object o1 = true ? new Integer(1) : new Double(2.0);

與下邊的等效嗎?

Object o2;

if (true)

o2 = new Integer(1);

else

o2 = new Double(2.0);

答案是并非如此,我們做個(gè)小測(cè)試。

System.out.println(o1);

System.out.println(o2);

程序的輸出是:

1.0

1

是的,在確有必要的情況下,條件表達(dá)式會(huì)升級(jí)數(shù)字類型。你希望這個(gè)程序拋出一個(gè)空指針異常嗎?

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);

更多細(xì)節(jié)請(qǐng)看這里。

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

5. 你也不懂復(fù)合賦值運(yùn)算符

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

i += j;

i = i + j;

直覺上,他們是等價(jià)的吧?事實(shí)上不是,Java 語言規(guī)范(Java Language Standard,JLS)中這樣寫道:

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

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

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

這種類型轉(zhuǎn)換很好的一個(gè)例子是使用 *= 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'

這個(gè)很有用吧?我會(huì)將它們應(yīng)用到我的程序里。原因你懂的。

6. 隨機(jī)數(shù)

這更像是一道題,先別看結(jié)果。看你自己能否找到答案。當(dāng)我運(yùn)行以下程序時(shí),

for (int i = 0; i < 10; i++) {

System.out.println((Integer) i);

}

有時(shí),我會(huì)得到以下輸出:

92

221

45

48

236

183

39

193

33

84

這是怎么回事?

答案已經(jīng)在前面劇透了……

答案在這里,需要通過反射來重載 JDK 中的 Integer 緩存,然后使用自動(dòng)裝箱(auto-boxing)和自動(dòng)拆箱(auto-unboxing)。千萬不要這么做,我們假設(shè)如果再做一次。

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

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

7. GOTO

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

int goto = 1;

結(jié)果將會(huì)是:

Test.java:44: error: expected

int goto = 1;

這是因?yàn)?goto 是一個(gè)保留的關(guān)鍵字,以防萬一……

但這不是最激動(dòng)人心的部分。最給力的是你可以通過 break、continue 以及標(biāo)簽代碼塊來實(shí)現(xiàn) goto。

向前跳轉(zhuǎn)

label: {

// do stuff

if (check) break label;

// do more stuff

}

字節(jié)碼:

2 ?iload_1 [check]

3 ?ifeq 6 ? ? ? ? ?// Jumping forward

6 ?..

向后跳轉(zhuǎn)

label: do {

// do stuff

if (check) continue label;

// do more stuff

break label;

} while(true);

字節(jié)碼:

2 ?iload_1 [check]

3 ?ifeq 9

6 ?goto 2 ? ? ? ? ?// Jumping backward

9 ?..

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

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

interface People => Set;

People 類型通過這個(gè)方法就可以與 Set 互換使用了:

People? ? ? ?p1 = null;

Set? p2 = p1;

People? ? ? ?p3 = p2;

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

class Test {

void x(I i, L l) {

System.out.println(

i.intValue() + ", " +

l.longValue()

);

}

}

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

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

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

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

9. 一些類型之間的關(guān)系是不確定的!

這個(gè)會(huì)很有趣的,所以來一杯咖啡然后集中注意力。假設(shè)以下兩種類型:

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

interface Type {}

class C implements Type> {}

class D

implements Type>>> {}

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

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

public abstract class Enum> { ... }

以上定義中, enum 的實(shí)現(xiàn)是一個(gè)純粹的語法糖。

// This

enum MyEnum {}

// Is really just sugar for this

class MyEnum extends Enum { ... }

記住這個(gè),讓我們?cè)倩氐絼偛拍莾蓚€(gè)類型。下邊的代碼可以通過編譯嗎?

class Test {

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

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

}

這是個(gè)很難的問題,Ross Tate 已經(jīng)回答了。答案是不確定的:

C 是 的子類型嗎?

Step 0) C

Step 1) Type>

Step 2) C ?(checking wildcard ? super C)

Step . . . (cycle forever)

然后

D 是 > 的子類型嗎?

Step 0) D >

Step 1) Type>>> >

Step 2) D >>

Step 3) Type>> >

Step 4) D> >>

Step . . . (expand forever)

嘗試在 Eclipse 中編譯以上代碼,Eclipse 會(huì)掛掉的!(不要擔(dān)心,我已經(jīng)提過 bug 了)

理解下這個(gè)…

Java 中的一些類型的關(guān)系是不確定的!

如果你想了解更多關(guān)于 Java 的這個(gè)特性,請(qǐng)閱讀 Ross Tate 與 Alan Leung 和 Sorin Lerner 共同編著的論文 “Taming Wildcards in Java’s Type System”或者我們自己總結(jié)的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 有個(gè)特性叫做類型交集。你可以聲明一個(gè)泛型,這個(gè)泛型是兩個(gè)類型的交集,例如:

class Test {

}

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

// Doesn't compile

Test s = null;

// Compiles

Test d = null;

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

void execute(T t) {}

你想要同時(shí)支持 Runnable 和 Serializable,是為了以防萬一要在網(wǎng)絡(luò)的另一處執(zhí)行它。Lambda 和序列化都有些古怪:

Lambda 表達(dá)式可以被序列化:

如果一個(gè) lambda 表達(dá)式的返回值和輸入?yún)?shù)可以被序列化,則這個(gè)表達(dá)式是可以被序列化的。

但即使這是真的,它也不會(huì)自動(dòng)繼承 Serializable 接口。你需要轉(zhuǎn)換才能成為那個(gè)類型。但如果你只是轉(zhuǎn)換為 Serializable…

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

lambda 就不支持 Runnable 了。

所以,

把它轉(zhuǎn)換為兩個(gè)類型:

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

結(jié)論

我經(jīng)常只這么說 SQL,但現(xiàn)在要用下邊的話來總結(jié)這篇文章了:

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,763評(píng)論 6 539
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,238評(píng)論 3 428
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,823評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,604評(píng)論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,339評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,713評(píng)論 1 328
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,712評(píng)論 3 445
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,893評(píng)論 0 289
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,448評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,201評(píng)論 3 357
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,397評(píng)論 1 372
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,944評(píng)論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,631評(píng)論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,033評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,321評(píng)論 1 293
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,128評(píng)論 3 398
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,347評(píng)論 2 377

推薦閱讀更多精彩內(nèi)容