1. 程式人生 > >java的奇技淫巧--意外行為與特性(譯文)

java的奇技淫巧--意外行為與特性(譯文)

Java是一種非常成熟的程式語言 - 事實上,它已經走過21年了,如果它是一個人,它可以在美國隨便混!隨著年齡的增長,智慧也在增長,而至少有時候,有些東西會變得很怪異。在本文中,我將介Java語言的一些奇技淫巧的行為和特徵。

在這裡,沒有特別的順序去介紹一系列Java的奇技淫巧,僅供娛樂,或者你向朋友們推介它吧。

Java有goto和const關鍵字

雖然Java沒有goto,但它確實作為保留關鍵字。const也是這樣。這意味著您無法這兩個名稱定義變數:

int goto = 0;

int const = 0;

這樣的定義是非法的,無法正常編譯!

用“_”符號定義數字

Java允許您使用“_”符號定義數字。因此,您可以像這樣編寫數值:

int thousand = 1_000;

double bigValue = 1_000_000.456_555;

long thisIsSilly = 3______4__3;

Double.MIN_VALUE不是許多人所假設的

為了展示Double.MAX_VALUE結果比預期的效果要完美,提供以下這樣的數值:1.7976931348623157E308。您認為Double.MIN_VALUE會為您帶來什麼?4.9E-324!好,執行開始 – 結果這個值大於0!

Double.MIN_VALUE是返回大於0的最小Double值(最小正數)。如果您想要最小的Double值,則需要使用:-Double.MAX_VALUE。他們實際上可以更好地命名這些東西,我想知道這樣的做法引起了多少人為的錯誤!

有趣的整數相等問題

談到這個錯誤......讓我告訴你一些迷惑不解的事情:

Integer ten = Integer.parseInt("10");

System.out.println(ten == Integer.valueOf(10));

//this is true

 

Integer thousand = Integer.parseInt("1000");

System.out.println(thousand == Integer.valueOf(1000));

//this is false

 

Integer物件的快取值的大小範圍是在[-128 127]區間。這意味著當我們在此數值範圍內操作時,“==”比較能正常返回結果。但當數值不在此範圍,物件相等的比較是否正常返回結果就很難說了!

想象一下,你可以編寫單元測試,且一切都正常執行,只要你沒有使用足夠大的數字,但這可能會導致嚴重的錯誤,所以為了安全 - 提醒:當你經常使用物件數值比較相等時,請使用“.equals()”,而不是依賴於“==”比較相等,除非你非常肯定這種做法沒有錯誤。

反射可以(大多數情況)做任何事情

這不應該作為一個Java奇技淫巧的內容,但通過反射,你可以重寫final值(大多數時間)並可訪問私有欄位......但不一定經常是這樣。

在我寫How to write horrible Java的文章時,當我重寫final值得時候,我發現了無法與預期結果一致的問題。Java中的常量,當final被內聯時(通指行內函數),即使你的程式碼看起來可以正常執行—沒有數值情況也會改變,這太不可思議了(檢視我的文章瞭解詳情和Stack Overflow答案)。

如果你需要,這是重寫final的數值程式碼例子:

 

public static void notSoFinal() throws NoSuchFieldException, IllegalAccessException, InterruptedException {

    ExampleClass example = new ExampleClass(10);

    System.out.println("Final value was: "+ example.finalValue);

    Field f = example.getClass().getDeclaredField("finalValue");

    Field modifiersField = Field.class.getDeclaredField("modifiers");

    modifiersField.setAccessible(true);

    modifiersField.setInt(f, f.getModifiers() & ~Modifier.FINAL);

    f.setInt(example, 77);

    System.out.println("Final value was: "+ example.finalValue);

}

 

你知道Java標籤是什麼玩意嗎?

好吧,我們離開上面調皮的話題,回到Java正規上來。你知不知道Java迴圈標籤?看一下這個迴圈語句:

 

outerLoop:

while (true) {

    System.out.println("I'm the outer loop");

    while(true){

        System.out.println("I am the inner loop");

        break outerLoop;

    }

}

使用標籤可以讓你在處理巢狀迴圈的時候,繼續或中斷一個特定的迴圈……在不同語言環境下,這個有點類似goto,

現在讓我們編寫一個編譯好了的,形跡可疑的程式碼:

int i = 3;

http://www.e4developer.com

while(i > 0){

System.out.println("http://www.e4developer.com");

i--;

}

 

可以正常編譯並且正常執行,因為它只是一個附加註釋的標記為http的迴圈。這會讓那些不熟悉標籤的人,使得它變得特別有趣!

列舉類

好吧,你可能知道這一點,但我還是想重複提一下。列舉是具有有限數量例項的特殊類。這意味著列舉可以:

實現介面

具有建構函式

實現不同方法

對於Scott Logic部落格,我寫了一篇名為 Java Enums – how to use them smarter 的文章,我展示了一些其他的使用方法。

For迴圈的靈活性

迴圈的標準,我相信你使用它們的次數,比你記憶中的還要多:

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

    //...

}

 

您知不知道里面所有條件是可選的嗎?你不需要初始化一個變數,你也不需要一個條件停止,你也不需要增加任何東西......如果省略所有內容,你最終會得到一個有趣的無限迴圈語法:

for(;;){

    //Infinite loop!

}

 

Java有初始化程式......以防萬一...

這是一個非常通用的特性,但是我還是依然會遇到一些具有經驗的Java開發人員,他們並不會真正意識到它的存在。在Java中,您可以寫類載入(靜態初始化程式)或建構函式(標準初始化程式)之前執行的程式碼塊。它是這樣的。

標準初始化程式:

int sum = 0;

{

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

        sum += 1;

    }

}

 

靜態初始化程式:

 

static double value = 0;

static {

    

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

        value += 1;

    }

}

 

只需要將這些程式碼塊放在類中,不要放在任何方法或建構函式中。

 

雙括號初始化集合

關於初始化的話題,我會向你展示Java初始化集合的奇技淫巧:

Map<String, String> map = new HashMap<String, String>() {{

    put("it", "really");

    put("works", "!");

}};

 

Set<String> set = new HashSet<String>() {{

    add("It");

    add("works");

    add("with");

    add("other");

    add("collections");

    add("too");

}};

 

它在Java中被稱為雙括號初始化,我從未見過這樣的寫法,被任何人使用過……難道是因為沒有人知道使用它嗎?

在發表這篇文章之後,很多讀者很快告訴我,這是我們應該避免的一個危險行為!比如應當使用輔助方法List.of()代替。(注意,雙括號初始化會派生匿名類。派生的類this可以指向外部的類。通常這不是什麼大問題,但在某些情況下使用不當會引起悲劇的問題,例如在序列化或垃圾回收時,應當注意這個問題)

 

Final值的可以放在後面初始化

這是一個小事情,但有些人認為,你在定義它們時時候,必須初始化常量值。實際情況不是這樣,您只需要初始化它們一次就夠了,你可以使用以下有效程式碼核對一下:

 

final int a;             

if(someCondition){       

    a = 1;               

} else {                 

    a = 2;               

}                         

System.out.println(a);  

 

當我們混合初始化程式碼塊和其他建構函式時,這麼做會變得棘手。

 

泛型擴充套件的橋接

儘管存在可疑的實現(型別擦除),但泛型在Java中還是非常強大。我吃驚的是,我們可以允許定義我們需要的泛型型別。看看這個例子:

public class SomeClass<T extends ClassA & InterfaceB & InterfaceC>

{}

你特別需要注意你定義的T,這特性會對你非常有用!

你還有更多嗎?

我希望你喜歡我分享的Java奇技淫巧。如果你還知道其他的Java特性和行為,並值得分享,請務必在評論或Twitter上告訴我!

 

 

原文:https://www.e4developer.com/2018/10/28/java-surprises-unexpected-behaviours-and-features/