讓 Java 8 改進我們的程序

這些文字寫的很遲,因爲 Java 8 已經發布六年有餘。

本文不是 Java 語法或 Java 8 新特性教程,故不涉及系統、細緻的用法介紹而是隻包含 Java 8 對舊程序的改進措施。

摘要

本文敘述了 Java 8 提供的新特性對現有代碼的改進方式以及帶來的好處。主要涉及比較重要的新特性:Optional、接口的默認方法、函數式、stream、CompletableFuture。

改進內容

Optional,更好的判別 null 的方式

Optional.ofNullable 可以用來構造一個可能爲 null 的 Optional 對象

public void myFunc(Tea tea) {
    Optional.ofNullable(tea).map(Tea::getName).orElse("錫蘭紅茶");
    // ......
}

值得注意的是,一般情況下我們不應該使用它的 isPresent 或 isEmpty 方法,不然就和以前的 if (a == null) 方式一樣了。

真正應該被關注的方法有:

  • orElse 或 orElseGet
  • filter
  • map
  • flatMap

這些方法靈活配合,可以實現嵌套判空、鏈式處理,又省事又不容易出錯。

給接口添加默認方法(default method)簡化代碼

在 Java 8 中,我們可以給結構添加默認的方法,如此一來,就不必讓每個實現類都重寫(override)一下這個方法。

最簡單的例子就是 JDK 中的 Iterable<T>,它提供默認方法 forEach,故名意思,對容器中每個元素進行某種動作。這樣的方法顯然沒必要讓每種容器(如 List,Queue)都重寫。

另一個例子是數據庫連接池 Druid,它在 1.2 版本開始用默認方法簡化了很多代碼。

值得注意的問題是接口的多繼承(多實現)問題,本來,Java 不允許類多繼承,避免了 C++ 中菱形繼承的問題,默認方法的特性又把這個問題帶回了 Java。解決辦法是顯示重寫方法。

interface A {
    default void foo() {
        System.out.println("A::foo");
    }
}

interface B {
    default void foo() {
        System.out.println("B::foo");
    }
}

class C implements A, B {

    // C 必須重寫 foo
    // 不然 Java 編譯器不知道咋弄

    @Override
    public void foo() {
        System.out.println("C::foo");
    }
}

public class App {
    public static void main(String[] args) {
        A a = new C();
        a.foo(); // C::foo
    }
}

在恰當的時候使用 lambda 表達式與流式操作

一些臨時函數應該用 lambda 表達式來代替。與舊式寫法相比,少了污染視線的工具類(礙事)。

在“對一組對象進行一系列工序”的場景下,相比於舊式寫法,用流式操作可以使程序簡潔、更可讀、更不易出錯:

例 1:把用逗號分隔的多個職位(positions)中的每個職位轉換到數字,然後取最大值。

final String[] ps = positions.split(",");
Arrays.stream(ps)
    .map(PositionToNumber::calc) // calc 是一個把 String 轉換成 long 的函數
    .max(Long::compareTo)
    .orElse(Long.MIN_VALUE);

例 2:把所有僱員按照職位級別排倒序。

employeeList
    .stream()
    .sorted(Comparator.comparing(Employee::getCachePositionLevel).reversed())
    .collect(Collectors.toList());

永遠使用新的日期時間 API

java.time.* 中的類(LocalDate,LocalTime,LocalDateTime 等)都是不可變的,所以完全不用擔心併發安全問題。時間之間的加減都會返回新對象而非修改已有對象。格式也統一爲 ISO-8601,不再有亂七八糟的各種格式。

of、with 系列的方法直觀好用。星期幾、月份也改成了枚舉。一系列細節上的優點令人舒坦。

JDBC 也規定了數據庫中日期時間相關類型也 Java 中日期時間類型的對應關係:

SQL Java
DATE LocalDate
TIME LocalTime
DATETIME LocalDateTime
TIMESTAMP LocalDateTime

併發工具類 StampedLock 的合理運用

在讀多寫少的時候,StampedLock 比 ReadWriteLock 性能更好,但它不支持重入,不支持條件變量。

另外,在 StampedLock 在上鎖後切勿調用線程的 interrupt,不然 CPU 使用率會立刻上去。

靈活選用 CompletableFuture 和 RxJava 實現異步

在 Java 中表示異步操作的傳統方式和大多數編程語言一樣:回調函數(方法)。但一旦這麼做,代碼會變得和亂柴火垛似的,維護代價極大,還容易藏着一些隱晦的 BUG。

Java 8 爲我們提供了新方法。

在異步場景不過於複雜時,CompletableFuture 是極好的異步操作工具。

CompletableFuture 的靜態方法 runAsync 和 supplyAsync 用來將一個操作異步化。supplyAsync 可以指定線程池,若不指定,則使用一個公共的線程池(CompletableFuture 類中的一個靜態私有變量(Executor))。

supplyAsync(supplier);
supplyAsync(supplier, executor);

應該根據實際場景,用合適的方式使用自己創建的線程池。例如,在 HTTP 客戶端開發中,每個 Client 中應該包含一個線程池,避免過多請求擁擠在一個線程池中。

CompletableFuture 實現了 CompletionStage,而 CompletionStage 提供了 then 系列方法(如 thenCombine,thenCombine)可以將操作組成流程圖,且支持條件執行和關係匯聚。

RxJava 不是 Java 8 纔有的(Java 6 即可支持),功能極爲強大。本文不是 RxJava 教程,此處從略。

根據使用場景,靈活選用二者。讓 Java 異步編程輕鬆搞定。

結束語

上面介紹了 Java 8 主要新特性如何改善程序,其實 Java 8 還有很多新的細節值得進一步挖掘。現在 Java 11 早已發佈,Java 17 也已經不遠,新時代的 Java 不僅提供了便利的特性和工具,也能更好地適應現代地基礎架構(如輕量級開發、容器化、Serverless),讓產品持續煥發活力。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章