Java14 都來了,你還不會用 Java8嗎?



Java 8 於2014年3月18日發佈,並且成為主流的 Java,如今,雖然 Java 14 都已經發布了,但是 開發者和公司選擇的版本依舊是經久不衰的 Java 8 版本,如果你還不瞭解這些新特性,是時候學習一下了。

Java 8 更新的一些重要功能一覽

Iterable 接口中的 forEach() 方法接口中的默認方法和靜態方法功能接口和 Lambda 表達式用於集合上批量數據操作的 Java Stream APIJava 時間 API集合 API 的改進併發 API 改進Java IO改進其他核心 API 改進

下面來簡要了解一下這些Java 8功能。我將提供一些代碼片段以更好地理解,因此,如果要在Java 8中運行程序,則必須按照以下步驟設置Java 8環境。

下載並安裝JDK8。像其他Java版本一樣,安裝也很簡單,運行下面的示例,必須要安裝 JDK 環境。

下載最新的 IDEA 開發環境,這裡我不推薦使用 Eclipse ,但是如果你有使用 Eclipse 習慣,那我在這裡推薦你可以嘗試 IDEA,因為它真的太棒啦~

Iterable 接口中的 forEach()方法

在 Java 8 以前,每當需要遍歷 Collection 時,就需要創建一個 Iterator 來進行迭代 Collection 對象,然後針對 Collection 中 的每個元素將業務邏輯循環在一起。如果迭代器使用不正確,可能會拋出 ConcurrentModificationException。

Java 8 在接口中引入了forEach方法,java.lang.Iterable因此在編寫代碼時,我們僅關注業務邏輯。forEach方法將java.util.function.Consumer對象作為參數,因此有助於將我們的業務邏輯放在一個可以重用的單獨位置。讓我們通過簡單的示例查看forEach用法。

<code>package com.journaldev.java8.foreach;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.function.Consumer;
import java.lang.Integer;

public class Java8ForEachExample {

    public static void main(String[] args) {

        //creating sample Collection
        List<integer> myList = new ArrayList<integer>();
        for(int i=0; i<10; i++) myList.add(i);

        //traversing using Iterator
        Iterator<integer> it = myList.iterator();
        while(it.hasNext()){
            Integer i = it.next();
            System.out.println("Iterator Value::"+i);
        }

        //traversing through forEach method of Iterable with anonymous class
        myList.forEach(new Consumer<integer>() {

            public void accept(Integer t) {
                System.out.println("forEach anonymous class Value::"+t);
            }

        });

        //traversing with Consumer interface implementation
        MyConsumer action = new MyConsumer();
        myList.forEach(action);

    }

}



//Consumer implementation that can be reused
class MyConsumer implements Consumer<integer>{

    public void accept(Integer t) {
        System.out.println("Consumer impl Value::"+t);
    }


}/<integer>/<integer>/<integer>/<integer>/<integer>/<code>

代碼的行數可能會增加,但是forEach方法有助於將迭代邏輯和業務邏輯放在不同的位置,從而使關注點和代碼更清晰地分離。

接口中的默認方法和靜態方法

如果仔細閱讀forEach方法的詳細信息,會注意到它是在Iterable接口中定義的,但我們知道接口不能具有方法主體。從Java 8開始,接口已增強為具有實現的方法。我們可以使用default和static關鍵字來創建帶有方法實現的接口。Iterable接口中的forEach方法實現為:

<code>default void forEach(Consumer super T> action) {
    Objects.requireNonNull(action);
    for (T t : this) {
        action.accept(t);
    }
}/<code>

我們知道Java不會在Class中提供多重繼承,因為它會導致Diamond問題。由於接口現在類似於抽象類,因此現在如何使用接口處理它。解決方案是在這種情況下編譯器將引發異常,我們將不得不在實現接口的類中提供實現邏輯。

<code>package com.journaldev.java8.defaultmethod;



@FunctionalInterface
public interface Interface1 {

    void method1(String str);

    default void log(String str){
        System.out.println("I1 logging::"+str);
    }

    static void print(String str){
        System.out.println("Printing "+str);
    }

    //trying to override Object method gives compile-time error as
    //"A default method cannot override a method from java.lang.Object"

//    default String toString(){
//        return "i1";
//    }

}/<code>

<code>package com.journaldev.java8.defaultmethod;

@FunctionalInterface
public interface Interface2 {

    void method2();

    default void log(String str){
        System.out.println("I2 logging::"+str);
    }

}/<code>

注意,兩個接口都有一個帶有實現邏輯的通用方法log()。

<code>package com.journaldev.java8.defaultmethod;

public class MyClass implements Interface1, Interface2 {

    @Override
    public void method2() {
    }

    @Override
    public void method1(String str) {
    }


    //MyClass won't compile without having it's own log() implementation
    @Override
    public void log(String str){
        System.out.println("MyClass logging::"+str);
        Interface1.print("abc");
    }

}/<code>

如你所見, Interface1 具有在 MyClass.log() 方法實現中使用的靜態方法實現。Java 8 在 Collection API 中大量使用默認和靜態方法,並且添加了默認方法,以便使 JDK 8 之前的代碼保持向後兼容。

如果層次結構中的任何類都具有具有相同的方法,則默認方法將變得無關緊要。由於任何實現接口的類都已經具有 Object 作為超類,因此如果接口中具有 equals(),hashCode() 默認方法,它將變得無關緊要。這就是為什麼為了更清楚起見,不允許接口具有Object默認方法。

有關Java 8接口特性的完整詳細信息,請閱讀 Java 8接口更改。

功能接口和Lambda表達式

如果你注意到上述接口代碼,則會注意到 @FunctionalInterface 註解。該功能接口是Java 8 中引入的新概念。具有一種抽象方法的接口就變成了功能接口。我們不需要使用 @FunctionalInterface 註解將接口標記為Functional Interface。@FunctionalInterface 註解是一種避免在功能接口中意外添加抽象方法的工具。您可以將其視為 @Override 批註,並且是使用它的最佳實踐。java.lang.Runnable 使用單個抽象方法 run() 是功能接口的一個很好的例子。

功能接口的主要優點之一是可以使用 lambda 表達式實例化它們。在 Java 8 之前,可以用匿名類實例化一個接口,但是代碼看起來很龐大。

<code>Runnable r = new Runnable(){
    @Override
    public void run() {
            System.out.println("My Runnable");
    }};/<code>

由於功能接口只有一種方法,因此 lambda 表達式可以輕鬆提供該方法的實現。只需要提供方法參數和業務邏輯。例如,可以使用 lambda 表達式編寫上面的實現:

<code>Runnable r1 = () -> {
            System.out.println("My Runnable");
        };/<code>

如果方法實現中只有一條語句,那麼我們也不需要花括號。例如,上面的Interface1匿名類可以使用lambda實例化,如下所示:

<code>Interface1 i1 = (s) -> System.out.println(s);

i1.method1("abc");/<code>

因此,lambda 表達式是輕鬆創建功能接口的匿名類的一種方法。使用 lambda 表達式對於代碼運行沒有任何的影響,因此要謹慎謹慎使用它,因為我們並不介意編寫一些額外的代碼行。

新包裝 java.util.function 添加了帶有功能接口束的,以提供 lambda 表達式和方法引用的目標類型。Lambda 表達式是一個非常複雜的話題,後續我會編寫一篇文章專門針對 lambda 表達式。

您可以在 Java 8 Lambda Expressions Tutorial 中閱讀完整的教程。

用於集合上批量數據操作的Java Stream API

java.util.stream 是Java 8中添加的一個新內容,以對該集合執行類似過濾/映射/遍歷的操作。Stream API 將允許順序執行和並行執行。這對我來說是非常好用的一個功能,因為我經常處理 Collections,而且通常使用很多的數據進行過濾數據,遍歷數據,stream 就完美的解決了這個問題。

Collection 接口已使用 stream() 和 parallelStream() 默認方法進行了擴展,以獲取用於順序執行和並行執行的 Stream ,用一個簡單的例子看看它們的用法。

<code>package com.journaldev.java8.stream;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

public class StreamExample {

    public static void main(String[] args) {

        List<integer> myList = new ArrayList<>();
        for(int i=0; i<100; i++) myList.add(i);

        //sequential stream
        Stream<integer> sequentialStream = myList.stream();

        //parallel stream
        Stream<integer> parallelStream = myList.parallelStream();

        //using lambda with Stream API, filter example
        Stream<integer> highNums = parallelStream.filter(p -> p > 90);
        //using lambda in forEach
        highNums.forEach(p -> System.out.println("High Nums parallel="+p));


        Stream<integer> highNumsSeq = sequentialStream.filter(p -> p > 90);
        highNumsSeq.forEach(p -> System.out.println("High Nums sequential="+p));

    }

}/<integer>/<integer>/<integer>/<integer>/<integer>/<code>

如果你運行示例代碼,將獲得如下輸出:

<code>High Nums parallel=91
High Nums parallel=96
High Nums parallel=93
High Nums parallel=98
High Nums parallel=94
High Nums parallel=95
High Nums parallel=97
High Nums parallel=92
High Nums parallel=99
High Nums sequential=91
High Nums sequential=92
High Nums sequential=93
High Nums sequential=94
High Nums sequential=95
High Nums sequential=96
High Nums sequential=97
High Nums sequential=98
High Nums sequential=99/<code>

請注意,並行處理的時候。值不按順序排列,因此在處理龐大的集合時,並行處理將非常有幫助。

這篇文章無法涵蓋有關Stream API的所有內容,您可以在 Java 8 Stream API Example Tutorial 中閱讀有關Stream API的所有內容。

Java 8 時間API

在之前的 Java 中使用日期,時間和時區一直很困難。Java 中沒有用於日期和時間的標準方法或 API。Java 8 有一個不錯的附加功能是 java.time 軟件包,它簡化了 Java 中使用時間的過程。

僅查看 Java Time API 軟件包,就可以感覺到它非常易於使用。它具有一些子包 java.time.format,這些子包提供用於打印和解析日期和時間的類,還有java.time.zone 提供對時區及其規則的支持。

新的 Time API 在整月的幾個月和一週中的幾天中都使用了枚舉而不是整數常量。常用的類之一是 DateTimeFormatter 將 DateTime 對象轉換為字符串。

有關完整的教程,請轉到 Java日期時間API示例教程。

集合API的改進

在上面的介紹已經看到了 forEach() 方法和用於集合的 Stream API。

Collection API 中添加的一些新方法是:

IteratorforEachRemaining(Consumer action) 在所有元素都已處理完畢或該動作引發異常之前,對其餘每個元素執行給定操作的默認方法。

CollectionremoveIf(Predicate filter) 刪除此集合中所有滿足給定謂詞的元素的默認方法。

Collection spliterator() 該方法返回Spliterator實例,該實例可用於順序或並行遍歷元素。地圖replaceAll(),compute(),merge()方法。

另外還有一些具有 hash 衝突 的HashMap 類的性能改進

併發API改進

一些重要的併發API增強功能包括:

ConcurrentHashMap compute(),forEach(),forEachEntry(),forEachKey(),forEachValue(),merge(),reduce()和search()方法。CompletableFuture 可以明確完成(設置其值和狀態)。Executors newWorkStealingPool() 使用所有可用處理器作為目標並行度級別創建竊取線程池的方法。

Java IO改進

我知道的一些IO改進:

Files.list(Path dir) 返回一個延遲加載的 Stream,其元素是目錄中的文件夾和文件列表。

Files.lines(Path path) 返回一個讀取指定文件所有行的文件流。

Files.find() 返回一個根據指定目錄搜索指定文件的文件列表流。

BufferedReader.lines() 返回一個Stream,其元素是從此 BufferedReader 中讀取的行。

其他核心API改進

一些雜項API改進在某些特殊情況可能會派上用場:

ThreadLocal 靜態方法可以使用 withInitial 方法創建實例。比較器接口已擴展了許多默認和靜態方法,用於自然排序,反向排序等。Integer,Long 和 Double 包裝器類中增加了 min(),max() 和 sum() 方法。Boolean 類中的 logicalAnd() ,logicalOr() 和 logicalXor() 方法。ZipFile.stream() 方法獲取 ZIP 文件條目上的有序Stream,並以壓縮時的順序出現在 Stream 中。Math 類中增加了幾種實用方法。jjs 添加命令以調用 Nashorn Engine。jdeps 添加命令以分析類文件JDBC-ODBC 橋已被刪除。PermGen 內存空間已被刪除


碼字不易,如果能幫到你,請點點關注~