1. 程式人生 > >Java 8 新語法習慣 (更輕鬆的函數語言程式設計)

Java 8 新語法習慣 (更輕鬆的函數語言程式設計)

作為一名 Java 程式語言的開發者,我們早已習慣了使用指令式程式設計和麵向物件物件,因為 Java 從第一個版本開始就是支援這些程式設計方式。然而在 Java 8 中我們獲得了一組強大的新的函式特性和語法。函數語言程式設計已經有十幾年的歷史,與面向物件的程式設計方式相比,函數語言程式設計更簡潔、更具表達力、更不容易出錯,而且更容易並行化。所以在 Java 程式中引入函式特性是非常必要的。函數語言程式設計需要我們對程式碼的設計方式進行一些改變。

我們學習本次內容之前需要更新我們電腦版本的 JDK 為至少 8 的版本。在本次章節將會解釋什麼是命令式、宣告式、和函式式以及他們之前的區別和共性。最後,將展示如何使用宣告式的思考方式過度到函數語言程式設計。

命令式格式

指令式程式設計的開發人員已經習慣了告訴程式做什麼和該如何做。下面是一個簡單的示例:

public class FindName {
    static List<String> nList = Arrays.asList("Dory","Gill","張三","趙四","劉能","謝飛機");

    public static void findName(List<String> names) {
        boolean found = false;
        for (String string : names) {
            if
(string.equals("趙四")) { found = true; break; } } if (found) { System.out.println("找到了 趙四"); }else { System.out.println("找不到 趙四"); } } public static void main(String[] args) { // TODO Auto-generated method stub
findName(nList); } }

執行結果:

找到了 趙四

findName() 方法首先初始化了一個 found 變數,也稱之為垃圾變數。我們通常會隨便為這個變數命名。接下來我們回去 names 列表中迴圈,一次處理一個元素。它檢查獲得的名稱是否與它尋找的值相等。如果是匹配的那麼就將 found 變數設定為 true,並結束控制流程。

這是一個指令式程式設計方式,是我們最熟悉的程式設計格式。我們需要定義程式中的每一步:迭代的每個元素,比較值,設定變數,跳出迴圈。命令格式為我們提供了安全的控制權限,這是好的。而另一方面,你需要執行所有的工作。在許多情況下我麼可以減少工作量來提高效率。

宣告式格式

宣告式程式設計意味著,我們仍然會告訴程式要做什麼,但是將實現細節留給底層的函式庫。我們重寫上面的程式碼:

public class FindNameTwo {
    static List<String> nList = Arrays.asList("Dory","Gill","張三","趙四","劉能","謝飛機");

    public static void findName(List<String> names) {
        if (names.contains("趙四")) {
            System.out.println("找到了 趙四");
        }else {
            System.out.println("找不到 趙四");
        }
    }

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        findName(nList);
    }

}

這個版本中沒有宣告任何垃圾變數。您也沒有精力浪費在對集合的處理上,而是使用內建的 contains() 方法來完成工作。我們仍然需要告訴程式要做什麼-檢查集合是否包含我們要尋找的值。但是卻將實現的細節留給了底層的方法。在指令式程式設計中我們自己控制著迭代,程式需要完全按照要求來操作。在宣告式程式設計中我們不必關心如何工作,只要結果符合預期就可以。花費更少的精力完成同樣的效果。

以宣告式程式設計思考,將簡化我們向 Java 函數語言程式設計的過度。因為函數語言程式設計是以宣告式為基礎建立的。

函式式格式

儘管函數語言程式設計始終是宣告式的,但是宣告式程式設計並不等於函數語言程式設計。函數語言程式設計是合併了宣告式方法和高階函式的模式。

Java 中的高階函式

在 Java 中,我們將物件傳遞給方法,在方法內建立物件,並從方法中返回物件。我們也可以將函式執行同樣的操作。也就是說可以將函式傳遞給方法,在方法內建立函式,並從方法返回函式。

在上下文中,方法是類的一部分。但是方法內的函式而言是本地函式,不能直接與類和物件關聯。我們定義:可以接收、建立或返回函式的函式或者方法被視為高階函式。

函數語言程式設計示例

先來看一箇舊的格式的示例,並逐步建立更復雜的程式。

public class UseMap {

    public static void incrementPageVisit(Map<String, Integer> pageVisits, String page) {
        if(!pageVisits.containsKey(page)) {
           pageVisits.put(page, 0);
        }

        pageVisits.put(page, pageVisits.get(page) + 1);
    }

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Map<String, Integer> pageVisits = new HashMap<>();            

        String page = "https://agiledeveloper.com";

        incrementPageVisit(pageVisits, page);
        incrementPageVisit(pageVisits, page);

        System.out.println(pageVisits.get(page));
    }

}

首先要看的是 incrementPageVisit() 方法,這是一種命令格式編寫的。它的作用是遞增給定的頁面計數,將該計數儲存在 Map 中。該方法不知道給定頁面是否有計數,所以會首先檢查是否存在計數。如果不存在就插入一個 0 作為該頁面的計數。然後遞增它,並將新值儲存在 Map 中。

宣告式程式設計要求我們將此方法從如何做轉變為做什麼。當呼叫 incrementPageVisit() 方法時,我們希望遞增計數。這就是做什麼。

宣告式程式設計我們應該掃描 JDK 庫,查詢 Map 介面是否有可以完成我們目標的方法。事實證明 merge() 方法能實現我們的目標。我們來修改 incrementPageVisit() 方法。但是本例我們並沒有採用更加智慧的宣告式格式程式設計;因為 merge() 是一個高階函式。

public static void incrementPageVisit(Map<String, Integer> pageVisits, String page) {
        pageVisits.merge(page, 1, (oldValue, value) -> oldValue + value);
    }

merge() 的第一個引數是 page:表示該鍵的值應該更新。第二個引數是分配給該鍵的初始值,如果 Map 中不存在該鍵初始化為 1。第三個引數是一個拉姆表示式,接收 map 中這個鍵的值作為引數。並且把這個引數做為變數傳遞給第二個引數。這個拉姆表示式返回的是它的引數的和,實際上就是遞增的計數。

比較 incrementPageVisit() 的一行程式碼與上面示例的多行程式碼。使用 merge() 程式設計就是一個函數語言程式設計的示例。由此看到理解宣告式方法有助於我們理解函數語言程式設計。(宣告式方法加高階函式)

總結

在 Java 程式中使用函式式方法和語法有很多好處:程式碼簡介、更富與表達、不易出錯、更容易並行而且通常比面向物件的程式碼更容易理解。函數語言程式設計儘管沒有那麼直觀,但是我們可以關注程式實現的目的而不是關注程式實現的方式。通過允許底層函式庫管理執行程式碼,我們將逐步直觀的瞭解高階函式,它是函數語言程式設計的構建基塊。

文章學習地址:

感謝 Venkat Subramaniam 博士

關注微信公眾號獲取免費的學習內容:
這裡寫圖片描述