溫故而知新,重溫 Java 7 的那些“新”特性

沉默王二發表於2019-07-29

2009 年 4 月 20 日,Java 的親生父親 Sun 被養父 Oracle 以 74 億美元收購,這在當時可是一件天大的事。有不少同學都擔心 Java 的前途,我當時傻不啦嘰地也很擔心:自己剛學會如何通過記事本編寫 Java 程式碼,然後通過 cmd 列印 Hello World 呢,這一下難道白學了?

但其實這種擔心是多餘的,因為 Java 並不會陪葬,畢竟行業內有太多基於 Java 的軟體系統在執行,Java 牽扯了太多人的飯碗。10 年過去了,Java 果然沒有陪葬,我仍然堅守在 Java 的陣線上。

2011 年 7 月 7 日,代號「海豚(Dolphin)」的 Java 7 首次推出,這也是 Java 歷史上一次非常重要的版本更新。同時推出了非常多實用的新特性,比如說建立泛型例項時自動型別推斷、switch-case 語句支援字串型別、新增 try-with-resources 語句等等。

這麼多年過去了,Java 7 的“新”特性顯然都變成老古董了——它們似乎也不需要我再贅述了,但好像不是這樣的。前幾天我發了一篇文章,用到了其中一個新特性,竟然有同學表示從來沒見過這個新特性,特意在交流群裡@我,要我說清楚怎麼回事(程式碼摺疊了,隨後貼出來)。

當時我就在想啊,原來技術從來沒有“新與舊”之說,只有知不知道。所以藉此機會,我就再來“贅述”一下 Java 7 的那些最經常使用的新特性吧。

01、數值中可使用下劃線分隔符聯接

之前圖片中的程式碼沒有展示全,現在我把具體的程式碼貼出來。

try {
    AsynchronousFileChannel channel = AsynchronousFileChannel.open(file);
    Future<Integer> result = channel.read(ByteBuffer.allocate(100_000), 0);
catch (IOException e) {
    e.printStackTrace();
}

其中 100_000 就是讀者要我解釋清楚的那個特性:在數值型別的字面值中使用下劃線分隔符聯接。

人腦不總是很善於記住很長串的數字,所以在處理長串數字時會採用分割法,比如說電話號碼要用一個分隔符“-”隔開,銀行卡號會每隔四位有一個空格等等。

數字中沒有用逗號(,)和中劃線(-)作為分隔符,是因為它們可能會引發歧義,取而代之的是下劃線(_)。這個不起眼的特性,讓我們開發人員在處理數字上輕鬆多了,畢竟 100_000100000 (忍不住查了一遍 0 的個數,害怕多寫或者少寫)辨識度高得多。

下劃線(_)位置並不固定,你可以隨意擺放,舉例如下:

int a = 100_000, b = 100000, c = 10_0000;
System.out.println(a==b); // true
System.out.println(b==c); // true
System.out.println(a==c); // true

需要注意的是,下劃線僅僅能在數字中間,編譯器在編譯的時候自己主動刪除數字中的下劃線。反編譯後的程式碼如下所示:

int a = 100000;
int b = 100000;
int c = 100000;
System.out.println(a == b);
System.out.println(b == c);
System.out.println(a == c);

02、 switch-case 語句支援字串型別

我們都知道,switch 是一種高效的判斷語句,比起 if/else 真的是爽快多了。示例如下:

String wanger = "王二";

switch (wanger) {
case "王二":
    System.out.println("王三他哥哥王二");
    break;
case "王三":
    System.out.println("王二他弟弟王三");
    break;

default:
    System.out.println("王二他妹妹王六");
    break;
}

switch-case 語句在處理字串的時候,會先將 switch 括號中的字串和 case 後的字串轉成 hashCode,所以字串不能為 null,否則會丟擲 NullPointerException。反編譯後的程式碼如下所示:

String wanger = "王二";
switch (wanger.hashCode()) {
    case 936926 :
        if (wanger.equals("王三")) {
            System.out.println("王二他弟弟王三");
            return;
        }
        break;
    case 937057 :
        if (wanger.equals("王二")) {
            System.out.println("王三他哥哥王二");
            return;
        }
}

System.out.println("王二他妹妹王六");

03、try-with-resources 語句

try-with-resources 的基本設想是把資源(socket、檔案、資料庫連線)的作用域限定在程式碼塊內,當這塊程式碼執行完後,資源會被自動釋放。

在此之前,資源的釋放需要在 finally 中主動關閉,不管 try 中的程式碼是否正常退出或者異常退出。就像下面這樣:

BufferedReader in = null;
try {
    in = new BufferedReader(new FileReader("cmower.txt"));
    int charRead;
    while ((charRead = in.read()) != -1) {
        System.out.printf("%c ", (char) charRead);
    }
catch (IOException ex) {
    ex.printStackTrace();
finally {
    try {
        if (in != null)
            in.close();
    } catch (IOException ex) {
        ex.printStackTrace();
    }
}

這樣的程式碼看起來就像老太婆的裹腳布,又臭又長;有了 try-with-resources 之後,情況大有改觀,不信你看:

try (BufferedReader in = new BufferedReader(new FileReader("cmower.txt"));) {
    int charRead;
    while ((charRead = in.read()) != -1) {
        System.out.printf("%c ", (char) charRead);
    }
catch (IOException ex) {
    ex.printStackTrace();
}

是不是清爽多了!把需要釋放的資源放在 try 後的 () 中,連 finally 也不需要了。不過,需要注意的是,上面的程式碼還需要優化,應該為每一個資源宣告獨立的變數,否則的話,某些特殊的情況下,資源可能無法正常關閉。

try (FileReader fr = new FileReader("cmower.txt"); 
    BufferedReader in = new BufferedReader(fr);) {
    int charRead;
    while ((charRead = in.read()) != -1) {
        System.out.printf("%c ", (char) charRead);
    }
catch (IOException ex) {
    ex.printStackTrace();
}

try-with-resources 特性依賴於一個新定義的介面 AutoCloseable,需要釋放的資源必須要實現這個介面。

不過,try-with-resources 在本質上仍然使用了 finally 去釋放資源,只不過這部分工作不再由開發者主動去做——從反編譯後的結果可以看得出來:

try {
    Throwable var1 = null;
    Object var2 = null;

    try {
        FileReader fr = new FileReader("cmower.txt");

        try {
            BufferedReader in = new BufferedReader(fr);

            int charRead;
            try {
                while ((charRead = in.read()) != -1) {
                    System.out.printf("%c ", (char) charRead);
                }
            } finally {
                if (in != null) {
                    in.close();
                }

            }

04、建立泛型例項時自動型別推斷

在這個特性出現之前,有關泛型變數的宣告略顯重複,示例如下:

Map<String, ArrayList<String>> wanger = new HashMap<String, ArrayList<String>>();

這樣的程式碼簡直太長了,很多重複的字元,難道編譯器不能推斷出泛型的型別資訊嗎?Java 7 實現了這個心願。

Map<String, List<String>> wanger = new HashMap<>();
List<String> chenmo = new ArrayList<>();
wanger.put("chenmo", chenmo);

這個看似簡單的特性省去了不少敲擊鍵盤的次數。

05、最後

除了上面我列出的這 4 個常用的新特性,Java 7 還有一些其他的特性,比如說 multi-catch,可以在一個 catch 語句中捕獲多個異常;比如說對集合(Collections)的增強支援,可以直接採用 []、{} 的形式存入物件,採用 [] 的形式按照索引、鍵值來獲取集合中的物件等等。

但總體上,我列出的那 4 個特性最為常用,其學習的意義更大。如果你覺得漏掉了某些更為常用的特性,歡迎你在文末提出來。

 

相關文章