1. 程式人生 > >還看不懂同事程式碼?快來補一波 Java 7 語法特性

還看不懂同事程式碼?快來補一波 Java 7 語法特性

前言

Java 平臺自出現到目前為止,已經 20 多個年頭了,這 20 多年間 Java 也一直作為最流行的程式設計語言之一,不斷面臨著其他新興程式語言的挑戰與衝擊。Java 語言是一種靜態強型別語言,這樣的語言特性可以讓 Java 編譯器在編譯階段發現錯誤,這對於構建出一個穩定安全且健壯的應用來說,尤為重要。但是也因為這種特性,讓 Java 開發似乎變得缺少靈活性,開發某些功能的應用時,程式碼量可能是其他語言的幾倍。Java 開發的不足之處也體現越來越複雜的 JDK 上,越來越複雜的 JDK 讓開發者完全理解的難度變的非常大。以至於開發者有時會重複實現一個 JDK 中已經提供了的功能。

為了跟上網際網路應用程式設計發展的腳步, Java 從 9 版本開始調整了 JDK 釋出的節奏,JDK 的每次更新都注重提高生產效率,提高 JVM 效能,推行模組化等,讓開發者可以更多的專注於業務本身,而不是浪費過多的時間在語言特性上。 Java 語言的更新要在語言的嚴謹性和靈活性上找到一個平衡點,畢竟靈活性可以減少編碼的複雜度,而嚴謹性是構建複雜且健壯應用的基石。

Java 7 語言特性

Java 重要的更新版本是在 Java 5 版本,這個版本中增加了如泛型、增強 for、自動裝箱拆箱、列舉型別,可變引數、註解等一系列重要功能,但是隨後的 Java 6 中並沒有增加新的重要的語言特性。Java 5 的釋出是在 2004 年,已經很久遠了,網上關於 Java 的教程也大多是基於 Java 6 的,也因此我準備從 Java 7 開始介紹每個 Java 版本的新特性。

下面所有程式碼的執行演示都是基於 Java 7 ,所以你如果嘗試下面的程式碼,需要安裝並配置 Jdk 1.7 或者已上版本。

1. switch String

在 Java 7 之前,switch 語法中只支援整數型別以及這些整數型別的封裝類進行判斷,在 Java 7 中,支援了 string 字串型別的判斷,使用起來非常的簡單,但是實用性是很高的。

1.1. switch String 基本用法

編寫一個簡單的 switch 判斷字串的測試類。

public class SwitchWithString {

    public static void main(String[] args) {
        String gender = "男";
        System.out.println(gender.hashCode());
        switch (gender) {
            case "男":
                System.out.println("先生你好");
                break;
            case "女":
                System.out.println("女士你好");
                break;
            default:
                System.out.println("你好");
        }
    }
}

switch 判斷字串使用起來很簡單,結果也顯而易見會先輸出 gender 變數的 hashCode,然後輸出匹配結果“先生你好”。

30007
先生你好

在使用 switch string 時候,如果結合 Java 5 的列舉類,那麼效果會更好,Java 7 之前使用列舉類要為每個值編數字代號,Java 7 之後可以直接定義字串名稱。

1.2. switch String 實現原理

但是這個支援只是編譯器層面的支援, JVM 依舊是不支援的。在對字串進行 switch 時,編譯器會把字串轉換成整數型別再進行判斷。為了驗證上面說的只是編譯器層面的支援,我們反編譯(可以使用 Jad 反編譯工具,也可以在 Idea 中雙擊編譯生成的 class )生成的 class 檔案,看到編譯器把 switch string 轉換成了字串 hashCode 判斷,為了防止 hashCode 衝突,又使用了 equals 再次判斷。

public class SwitchWithString {
    public SwitchWithString() {
    }
    
    public static void main(String[] args) {
        String gender = "男";
        System.out.println(gender.hashCode());
        byte var3 = -1;
        switch(gender.hashCode()) {
        case 22899:
            if (gender.equals("女")) {
                var3 = 1;
            }
            break;
        case 30007:
            if (gender.equals("男")) {
                var3 = 0;
            }
        }

        switch(var3) {
        case 0:
            System.out.println("先生你好");
            break;
        case 1:
            System.out.println("女士你好");
            break;
        default:
            System.out.println("你好");
        }

    }
}

2. try-with-resource

Java 不同於 C++,需要開發者自己管理每一塊記憶體,大多時候 Java 虛擬機器都可以很好的幫我們進行資源管理,但是也有時候需要手動釋放一些資源,比如資料庫連線、磁碟檔案連線、網路連線等。換句話說,只要是資源數量有限的,都需要我們手動的進行釋放。

2.1. try-catch-finally

在操作有限資源的時候,可能會出現各種異常,不管是讀取階段還是在最後關閉資源的過程中,都有可能出現問題,我們通常會使用下面的方式 try-catch-finally 保證資源的釋放。

像下面這樣。

/**
 * 釋放資源
 *
 * @author www.codingme.net
 */
public class TryCatachFinally {

    /**
     * 異常處理
     *
     * @param args
     */
    public static void main(String[] args) throws Exception {
        FileInputStream inputStream = null;
        try {
            inputStream = new FileInputStream("jdk-feature-7.iml");
        } catch (FileNotFoundException e) {
            throw e;
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    throw e;
                }
            }
        }
    }
}

看看這噁心的程式碼結構,為了捕獲異常,我們寫了一個 catch,為了能保證釋放資源,我們又寫了 finally 進行資源釋放,在資源釋放時為了捕捉 close 時丟擲的異常,我們又寫了一個 try-catch。最後看著這複雜的程式碼,如果有人告訴你這段程式碼有 bug,那你一定不會相信。但是確實是這樣,看起來嚴密的程式碼邏輯,當 try 中的程式碼邏輯和 close 方法同時產生異常的時候,try 中的異常資訊會丟失。

可以看這裡例子。

package net.codingme.feature.jdk7;

import java.io.IOException;

/**
 * 釋放資源
 *
 * @author www.codingme.net
 */
public class TryCatachFinallyThrow {

    /**
     * 異常處理
     *
     * @param args
     */
    public static void main(String[] args) throws Exception {
        read();
    }

    public static void read() throws Exception {
        FileRead fileRead = null;
        try {
            fileRead = new FileRead();
            fileRead.read();
        } catch (Exception e) {
            throw e;
        } finally {
            if (fileRead != null) {
                try {
                    fileRead.close();
                } catch (Exception e) {
                    throw e;
                }
            }
        }

    }
}

class FileRead {

    public void read() throws Exception {
        throw new IOException("讀取異常");
    }

    public void close() throws Exception {
        System.out.println("資源關閉");
        throw new IOException("關閉異常");
    }
}

很明顯程式碼裡 readclose 方法都會產生異常,但是執行程式發現只能收到 close 的異常資訊。

資源關閉
Exception in thread "main" java.io.IOException: 關閉異常
    at net.codingme.feature.jdk7.FileRead.close(TryCatachFinallyThrow.java:51)
    at net.codingme.feature.jdk7.TryCatachFinallyThrow.read(TryCatachFinallyThrow.java:33)
    at net.codingme.feature.jdk7.TryCatachFinallyThrow.main(TryCatachFinallyThrow.java:20)

異常資訊丟失了,可怕的是你以為只是 close 時發生了異常而已。

2.2. try-autocloseable

上面的問題在 Java 7 中其實已經提供了新的解決方式,Java 7 中對 try 進行了增強,可以保證資源總能被正確釋放 。使用增強 try 的前提是 try 中的類實現了 AutoCloseable 介面,在 Java 7 中大量的需要釋放資源的操作其實都已經實現了此介面了。

實現了 AutoCloseable 的類,在增強 try中使用時,不用擔心資源的關閉,在使用完畢會自動的呼叫 close方法,並且異常不會丟失。

讓我們編寫的模擬資源操作的類實現 AutoCloseable 介面,然後時候增強 try 看看效果。

package net.codingme.feature.jdk7;

/**
 * 自動關閉
 *
 * @author www.codingme.net
 */
public class AutoCloseResource {
    public static void main(String[] args) throws Exception {
        try (Mysql mysql = new Mysql();
             OracleDatabase oracleDatabase = new OracleDatabase()) {
            mysql.conn();
            oracleDatabase.conn();
        }
    }
}

class Mysql implements AutoCloseable {

    @Override
    public void close() throws Exception {
        System.out.println("mysql 已關閉");
    }

    public void conn() {
        System.out.println("mysql 已連線");
    }
}

class OracleDatabase implements AutoCloseable {

    @Override
    public void close() throws Exception {
        System.out.println("OracleDatabase 已關閉");
    }

    public void conn() {
        System.out.println("OracleDatabase 已連線");
    }
}

測試類 Mysql 和 OracleDatabase 都是實現了 AutoCloseable,執行檢視結果。

mysql 已連線
OracleDatabase 已連線
OracleDatabase 已關閉
mysql 已關閉

確認在發生異常時候異常資訊不會丟失,寫一個有異常的模擬測試類進行測試。

package net.codingme.feature.jdk7;

import java.io.IOException;

/**
 * 釋放資源
 *
 * @author www.codingme.net
 */
public class AutoCloseThrow {

    public static void main(String[] args) throws Exception {
        try (FileReadAutoClose fileRead = new FileReadAutoClose()) {
            fileRead.read();
        }
    }
}

class FileReadAutoClose implements AutoCloseable {

    public void read() throws Exception {
        System.out.println("資源讀取");
        throw new IOException("讀取異常");
    }

    @Override
    public void close() throws Exception {
        System.out.println("資源關閉");
        throw new IOException("關閉異常");
    }
}

執行檢視異常資訊。

資源讀取
資源關閉
Exception in thread "main" java.io.IOException: 讀取異常
    at net.codingme.feature.jdk7.FileReadAutoClose.read(AutoCloseThrow.java:23)
    at net.codingme.feature.jdk7.AutoCloseThrow.main(AutoCloseThrow.java:14)
    Suppressed: java.io.IOException: 關閉異常
        at net.codingme.feature.jdk7.FileReadAutoClose.close(AutoCloseThrow.java:29)
        at net.codingme.feature.jdk7.AutoCloseThrow.main(AutoCloseThrow.java:15)

自動關閉,異常清晰,關閉異常存在於 Suppressed ,稱為抑制異常,後續文章會詳細介紹。

3. try-catch

在 Java 7 之前,一個 catch 只能捕獲一個異常資訊,當異常種類非常多的時候就很麻煩,但是在 Java 7 中,一個 catch 可以捕獲多個異常資訊,每個異常捕獲之間使用 | 分割,

package net.codingme.feature.jdk7;

import java.io.IOException;

/**
 * 多異常捕獲
 */
public class TryCatchMany {

    public static void main(String[] args) {
        try (TxtRead txtRead = new TxtRead()) {
            txtRead.reader();
        } catch (IOException | NoSuchFieldException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

class TxtRead implements AutoCloseable {

    @Override
    public void close() throws Exception {
        System.out.println("資源釋放");
    }

    public void reader() throws IOException, NoSuchFieldException {
        System.out.println("資料讀取");
    }
}

需要注意的是,一個 catch 捕獲多個異常時,不能出現重複的異常型別,也不能出現一個異常型別是另一個類的子類的情況。

4. 二進位制

Java 7 開始,可以直接指定不同的進位制數字。

  1. 二進位制指定數字值,只需要使用 0b 或者 OB 開頭。
  2. 八進位制指定數字值,使用 0 開頭。
  3. 十六進位制指定數字值,使用 0x 開頭。
/**
 * 二進位制
 *
 * @author www.codingme.net
 */
public class Binary {
    public static void main(String[] args) {
        // 二進位制
        System.out.println("------2進位制-----");
        int a = 0b001;
        int b = 0b010;
        System.out.println(a);
        System.out.println(b);
        // 八進位制
        System.out.println("------8進位制-----");
        int a1 = 010;
        int b1 = 020;
        System.out.println(a1);
        System.out.println(b1);
        // 十六進位制
        System.out.println("------16進位制-----");
        int a2 = 0x10;
        int b2 = 0x20;
        System.out.println(a2);
        System.out.println(b2);
    }
}

輸出結果。

------2進位制-----
1
2
------8進位制-----
8
16
------16進位制-----
16
32

5. 數字下劃線

Java 7 開始支援在數字定義時候使用下劃線分割,增加了數字的可讀性。

/**
 * 數字下環線
 *
 * @author www.codingme.net
 */
public class NumberLine {
    public static void main(String[] args) {
        int a = 1_000;
        int b = 1_0__0_0_0_____00;
        System.out.println(a);
        System.out.println(b);
    }
}

得到結果。

1000
1000000

6. 結束語

雖然 Java 7 早在 2011 年就已經發布了,但是據我發現,使用到 Java 7 開始的新特性新語法的並不多,所以我的 JDK 新特性系列文章計劃從 Java 7 開始,一直介紹到目前已經發布的 Java 13,以後 Java 新版本更新的同時,這個新特性系列文章也會持續更新。

此去山高水遠,願能一路堅持,願你我一路同行。

<完>
個人網站:https://www.codingme.net
如果你喜歡這篇文章,可以關注公眾號,一起成長。
關注公眾號回覆資源可以沒有套路的獲取全網最火的的 Java 核心知識整理&面試核心資料。