JAVA8新特性介紹

JAVA8新特性介紹

特性介紹

Lambda表達式(函數式編程)

lambda 表達式讓你用一種簡潔的方式去避免一大塊的代碼。例如,你需要一個線程來執行一個任務。需要創建一個 Runnable 對象,然後做爲參數傳遞給 Thread。

new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello World");
    }
}).start();

使用lambda表達式的話,可以簡化很多

new Thread(() -> System.out.println("Hello World")).start();

方法引用

方法引用可以理解爲一個特殊的 lambda 表達式,也是一個新的特性。它可以快速的選擇定義在類中的已經存在的方法。例如:輸出一個字符串,通常可以這樣寫(JAVA8):

Arrays.asList(1, 2, 3, 4, 5).forEach(new Consumer<Integer>() {
    @Override
    public void accept(Integer integer) {
        System.out.println(integer);
    }
});

使用lambda表達式的話:

Arrays.asList(1, 2, 3, 4, 5).forEach(integer -> System.out.println(integer));

改用方法引用會更加簡潔:

Arrays.asList(1, 2, 3, 4, 5).forEach(System.out::println);

符號::是方法引用時特有的。

Streams

幾乎每一個 JAVA 應用程序都會創建和處理集合。它們是許多程序處理任務的基石,集合可以用來聚合及處理數據。然而,處理集合過於繁瑣而且難以處理併發。比如,從一個發票列表中找到“聚餐”相關,且金額在200元以上的發票ID,並按按發票金額的數值排序:

List<Invoice> invoices = new ArrayList<>();
List<Invoice> selectedInvoices = new ArrayList<>();
for (Invoice inv : invoices) {
    if (inv.getTitle().contains("聚餐") && inv.getAmount() > 200) {
        selectedInvoices.add(inv);
    }
}
Collections.sort(selectedInvoices, new Comparator<Invoice>() {
    public int compare(Invoice inv1, Invoice inv2) {
        // 倒序
        return inv2.getAmount().compareTo(inv1.getAmount());
    }
});
List<Integer> selectedInvoicesIds = new ArrayList<>();
for (Invoice inv : selectedInvoices) {
    selectedInvoicesIds.add(inv.getId());
}

上面冗長的判斷,是計算機應該執行的命令。而使用JAVA8中引入的流來處理的話,則不太一樣了,可以很直觀的看到每一步做的處理。

List<Invoice> invoices = new ArrayList<>();
List<Integer> selectedInvoicesIds = invoices.stream()
        .filter(inv -> inv.getTitle().contains("聚餐") && inv.getAmount() > 200)
        .sorted(Comparator.comparingDouble(Invoice::getAmount).reversed())
        .map(Invoice::getId)
        .collect(Collectors.toList());

甚至在Streams中,可以簡單的通過將invoices.stream()替換成invoices.parallelStream()就實現了集合的併發處理。當然,並不是所有情況都適合用併發處理。

另外,上面沒有考慮空指針的情況,請看Optional

Optional

JAVA8 中引入了一個新的類叫做 Optional。很多編程語言都可以自動處理空值,而JAVA沒有,這就導致了空指針異常這個幾乎每個人都遇到過,也深感邪惡的異常。Optional靈感來自於函數式編程語言,它的引入是爲了當值爲空時代碼可以爭取地執行。

Optional是一種單值容器,這種情況下如果沒有值則爲空。Optional 很久以前已經在第三方集合框架(比如 Guava)中可用,但現在它作爲 JAVA API 的一部分,可用於JAVA中。事實上,Optional 定義了方法強制你去明確地檢查值存在還是缺省。

上述代碼中,inv.getTitle()inv.getAmount()都沒有判斷空,這顯然在很有可能的情況下導致空指針異常,修改如下:

List<Invoice> invoices = new ArrayList<>();
List<Integer> selectedInvoicesIds = invoices.stream()
        .filter(inv -> Optional.ofNullable(inv.getTitle()).orElse("").contains("聚餐") && Optional.ofNullable(inv.getAmount()).orElse(0d) > 200)
        .sorted(Comparator.comparingDouble((ToDoubleFunction<Invoice>) item -> Optional.ofNullable(item.getAmount()).orElse(0d)).reversed())
        .map(Invoice::getId)
        .collect(Collectors.toList());

顯得有一些雜亂,但是比起寫很多if else判斷來避免空指針,這種方式顯然更加方便。

簡單示例
再看一個簡單的例子,如果有一個門店信息,要獲取它所在的省份和城市:

City city = store.getProvince().getCity()

如果省份沒有設置,那不可避免會帶來空指針異常,但是如果需要判斷的話,你得像下面這樣:

City city = null;
if (store != null) {
    Province province = store.getProvince();
    if (province != null) {
        city = province.getCity();
    }
}

而使用 Optional 包一層的話,簡單,且可以完全避免異常:

City city = Optional.ofNullable(store).map(Store::getProvince).map(Province::getCity).orElse(null);

Optional 的map方法會判斷值是否爲空,所以可以很好的避免空帶來的異常。

CompletableFuture

在併發編程中,我們通常會用到一組非阻塞的模型:Promise,Future 和 Callback。其中的 Future 表示一個可能還沒有實際完成的異步任務的結果,針對這個結果可以添加 Callback 以便在任務執行成功或失敗後做出對應的操作,而 Promise 交由任務執行者,任務執行者通過 Promise 可以標記任務完成或者失敗。 可以說這一套模型是很多異步非阻塞架構的基礎。

這一套經典的模型在 Scala、C# 中得到了原生的支持,但 JAVA8 之前並沒有支持 Callback 的 Future 可用,當然也並非在 JAVA 界就沒有發展了,比如 Guava 就提供了ListenableFuture 接口,而 Netty 4+ 更是提供了完整的 Promise、Future 和 Listener 機制。

JAVA8中提供 CompletableFuture 作爲支持回調的 Future,在異步場景下,不再需要手動調用future.get()來等待結果,只需要提供complete,exceptionally,thenApply,thenAccept,thenRun就可以使用回調機制,輕鬆處理“完成後回調”、“異常時回調”、“繼續執行”、“接受結果”、“繼續運行(此處多爲依賴的異步調用)”。

CompletableFuture 可以使用的場景足以獨立成文,而且文章不少呢,不再贅述。CompletableFuture 在異步/回調上和 RxJava 是類似的,但是 RxJava 可以處理的場景更加豐富。

接口default方法

JAVA8 中對接口進行了兩大改造,使其可以在接口中聲明具體的方法。

引入默認方法

它可以在接口聲明的方法中增加實現體,作爲一種將 JAVA API 演變爲向後兼容的機制。例如,可以看到在 JAVA8 的 List 接口中現在支持一種排序方法,像下面這麼定義的:

default void sort(Comparator<? super E> c) {
    Object[] a = this.toArray();
    Arrays.sort(a, (Comparator) c);
    ListIterator<E> i = this.listIterator();
    for (Object e : a) {
        i.next();
        i.set((E) e);
    }
}

接口現在也可以擁有靜態方法

它和定義一個接口,同時用一個內部類定義一個靜態方法去進行接口的實例化是同一種機制。例如,JAVA 中有 Collection 接口和定義了通用靜態方法的 Collections 類,現在這些通用的靜態也可以放在接口中。例如,JAVA8 中的 stream 接口是這樣定義靜態方法的:

public static<T> Stream<T> of(T... values) {
    return Arrays.stream(values);
}

新的Date和Time接口(類似JodaTime)

JAVA8 引入了一套新的日期時間 API ,修復了之前舊的 Date 和 Calendar 類的許多問題。這一套新的Date和Time的時間API風格,基本和Joda-Time一致,畢竟Joda-Time框架的作者正是JSR-310的規範的倡導者。

這套新的日期時間 API 包含兩大主要原則:

領域驅動設計

新的日期時間 API 採用新的類來精確地表達多種日期和時間的概念。例如,可以用 Period 類去表達一個類似於 “2個月零3天(63天)”,用 ZonedDateTime 去表達一個帶有時間區域的時間。每一個類提供特定領域的方法且採用流式風格。因此,可以通過方法鏈寫出可讀性更強的代碼。

不變性

Date(日期) 和 Calendar(日曆)的其中一個問題就是他們是非線程安全的。此外,當使用 Date 作爲API的一部分時,Date 的值可能會被意外的改變。爲了避免這種潛在的BUG,在新的日期時間 API 中的所有類都是不可變的。

也就是說,在新的日期時間 API 中,你不能改變對象的狀態,取而代之的是,你調用一個方法會返回一個帶有更新的值的新對象。

帶來的好處

代碼可讀性

JAVA 寫出來的代碼,大多是比較繁瑣的,這導致了可讀性的降低。換句話說,它需要很多代碼才能表達一個簡單的概念。

舉個例子:簡單的遞減排序

List<Integer> integers = Arrays.asList(1, 34, 771, 14, 3, 5, 299);
Collections.sort(integers, new Comparator<Integer>() {
    public int compare(Integer a, Integer b) {
        return Integer.compare(b, a);
    }
});
System.out.println(integers);

你知道什麼時候是a-b,什麼時候是b-a嗎?

排序的時候,要關注具體哪個值減哪個值,學 C 語言的時候就知道的,根據返回值是大於零、等於零、小於零來判斷。但是可讀性差,而且每次排序都要關注這個細節,非常繁瑣。

而使用JAVA8 的流、以及新的接口方法,可以簡單的排序,並且可以不關注排序細節。

List<Integer> integers = Arrays.asList(1, 34, 771, 14, 3, 5, 299);
integers.sort(Comparator.comparingInt(Integer::intValue).reversed());
System.out.println(integers);

提升多核心處理能力

這裏的最佳體現就是並行流。我們平時的代碼中有大量的集合處理,而通常情況下,都是串行處理的,畢竟寫一個並行處理集合的程序太過於複雜了,提升的速度可能不及帶來BUG的風險。並行流的存在,非常簡單的解決了這個問題。

當然,還有CompletableFuture,它的出現也是爲了能夠在更多需要使用異步編程的時候,可以簡單地實現,而不是寫出可能帶有一堆BUG的異步程序。

總結

總之,一切都在向好的方向發展,更專業的人做更專業的事。複雜的並行、異步,交給更深入底層的人來做。我們做好業務上需要的併發和異步。

函數式的編程,帶來了非常強的代碼可讀性,又可以避免空指針異常,非常適合在流程性業務邏輯中使用起來。易讀的代碼,也會給日後的維護帶來遍歷。

我建議,大家都用JAVA8,將這些新特性都用上,很明顯,它們比之前的版本好太多了。

參考文獻:

譯文《JAVA8開發指南》爲什麼你需要關注 JAVA8

原文《JAVA8開發指南》

併發編程 Promise, Future 和 Callback

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