Java8 新特性

本文內容如有錯誤、不足之處,歡迎技術愛好者們一同探討,在本文下面討論區留言,感謝。

簡述

Java 8 (又稱爲 jdk 1.8 ) 是 Java 語言開發的一個主要版本。 Oracle 公司於 2014 年 3 月 18 日發佈 Java 8 ,它支持函數式編程,新的 JavaScript 引擎,新的日期 API,新的 Stream API 等。

java8

新功能

列表
  1. Iterable 接口中的 forEach() 方法
  2. 接口中的默認方法和靜態方法
  3. 功能接口和 Lambda 表達式
  4. Stream API 操作集合批量數據
  5. Date Time API 加強對日期與時間的處理
  6. 集合 API 改進
  7. 併發 API 改進
  8. Optional 類,解決空指針問題
  9. 新工具 新的編譯工具
詳細介紹
1. Iterable 接口中的 forEach() 方法

當程序需要遍歷 Collection 時,將會創建一個 Iterator 其目的是進行迭代集合的全部對象,然後針對 Collection 中的每個元素將業務邏輯循環聯繫在一起。如果迭代器使用不正確,程序會拋出 ConcurrentModificationException

Java 8 在接口中引入了 forEach 方法,java.lang.Iterable 因此在編寫代碼時,開發人員僅需要關注業務邏輯即可。forEach 方法將 java.util.function.Consumer 對象作爲參數,因此有助於編寫可重用的業務邏輯代碼。

package iterable.java8;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class IterableDemo {
    public static void main(String[] args) {

        // 創建集合
        List<Integer> myList = new ArrayList<Integer>();
        for(int i=0; i<10; i++) myList.add(i);

        // 使用 迭代器 iterator
        Iterator<Integer> it = myList.iterator();
        while(it.hasNext()){
            Integer i = it.next();
            System.out.println("Iterator Value::"+i);
        }

        // 使用forEach表達式
        myList.forEach(t -> System.out.println("forEach anonymous class Value::"+t));
    }

}

使用 java.util.function.Consumer

package iterable.java8;

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

public class IterableConsumerDemo {
    public static void main(String[] args) {

        // 創建集合
        List<Integer> myList = new ArrayList<Integer>();
        for(int i=0; i<10; i++) myList.add(i);

        // 使用 迭代器 iterator
        Iterator<Integer> it = myList.iterator();
        while(it.hasNext()){
            Integer i = it.next();
            System.out.println("Iterator Value::"+i);
        }

        // 使用forEach表達式,使用 Consumer
        myList.forEach(new Consumer<Integer>() {

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

        });

        // 執行 子定義Consumer
        MyConsumer action = new MyConsumer();
        myList.forEach(action);

    }

}

// 實現 Consumer 接口
class MyConsumer implements Consumer<Integer> {

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

Consumer 接口源碼:

/**
 * 接受單個輸入參數且不返回結果的操作
 * @since 1.8
 */
@FunctionalInterface
public interface Consumer<T> {

    /**
     * 對給定參數執行此操作。
     *
     * @param t 輸入參數,是個泛型
     */
    void accept(T t);

    /**
     * 對給定參數執行此操作。
     * 返回一個組合的 Consumer ,該組合 Consumer 將會依次執行accept方法定義的操作和 after 參數的 accept 操作。
     * 如果執行任何一個操作都會引發異常,則會將該異常拋出到調用該組合操作的調用方。
     * 如果執行此操作引發異常,不執行 after  參數的 accept 操作。
     *
     * @param after 當前對象執行accept操作後執行after中的accept操作  接口的默認實現
     */
    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

Iterable 源代碼

public interface Iterable<T> {

    Iterator<T> iterator();

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

    default Spliterator<T> spliterator() {
        return Spliterators.spliteratorUnknownSize(iterator(), 0);
    }
}
2. 接口中的默認方法和靜態方法

仔細閱讀上面的源碼,可以很容易發現 forEach() 方法是在 Iterable 接口中定義的,但是開發人員都很清楚接口不能具有方法主體。從 Java 8 開始,接口已增強爲具有實現的方法。使用 defaulestatic 關鍵字來創建帶有方法實現的接口。

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

定義一個接口 Interface1

package iterable.java8;

@FunctionalInterface
public interface Interface1 {

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

}

接口 Interface2

package iterable.java8;

@FunctionalInterface
public interface Interface2 {

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

}

實現接口 Interface1Interface2

package iterable.java8;

public class InterfaceImplementDemo implements Interface1,Interface2 {
    @Override
    public void active(String str) {

    }

    /**
     * 必須實現 log 這個方法
     * @param str
     */
    @Override
    public void doSomething(String str) {
       System.out.println("Implement doSomething::"+str);
    }
}

Java 8 在 Collection API 中大量使用默認 default 和靜態 static 方法,並且添加了默認方法,以便代碼保持向下兼容。

3. 功能接口和 Lambda 表達式

什麼是功能接口?

具有一種抽象方法的接口,使用 @FunctionalInterface 註解進行表明。

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

Runnable r = new Runnable(){
			@Override
			public void run() {
				System.out.println(" Impetment Runnable");
			}};
			
Runnable r1 = () -> {
			System.out.println("Lambda Runnable");
		};			

因此,lambda 表達式可以輕鬆創建功能接口的匿名類的方法。由於,使用 lambda 表達式沒有運行時的好處,一方面調試困難,另一方面維護者需要閱讀整個 lambda 才能夠理解其邏輯,因此建議謹慎使用它。

4. Stream API 操作集合批量數據

新添加的Stream API(java.util.stream) 把真正的函數式編程風格引入到Java中。

Stream 是對集合對象功能的增強,專注於對集合對象進行便利、高效的操作,或者進行大批量數據操作 ,就是一種流式處理,所有的數據像一條河流一樣,Stream 中的函數就是針對這條數據河流進行相對應的處理,例如:filter 是對河流中符合對應的邏輯判斷進行篩選。

下面舉例說明:

public class StreamDemo {

    public static void main(String[] args) {
        List<Integer> listData = new ArrayList<>();
        for (int i = 0; i < 30; i++) {
            listData.add(i);
        }

        // 1. 獲取流
        Stream<Integer> streamListData = listData.stream();

        // 2. 過濾流 獲取集合中從1到10的數據集合
        Stream<Integer> limit1To10 = streamListData.filter(p -> p >= 1 && p <= 10);

        // 3. 打印信息
        limit1To10.forEach(p -> System.out.println("limit1To10 Nums ="+p));

    }
}

輸出結果:

limit1To10 Nums =1
limit1To10 Nums =2
limit1To10 Nums =3
limit1To10 Nums =4
limit1To10 Nums =5
limit1To10 Nums =6
limit1To10 Nums =7
limit1To10 Nums =8
limit1To10 Nums =9
limit1To10 Nums =10

Stream 還可以進行並行執行,下面介紹一下 並行執行的例子:

package iterable.java8;

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

public class StreamDemo {

    public static void main(String[] args) {
        List<Integer> listData = new ArrayList<>();
        for (int i = 0; i < 30; i++) {
            listData.add(i);
        }

        // 1. 獲取流
        Stream<Integer> streamListData = listData.parallelStream();


        // 2. 過濾流 獲取集合中從1到10的數據集合
        Stream<Integer> limit1To10 = streamListData.filter(p -> p >= 1 && p <= 10);

        // 3. 打印信息
        limit1To10.forEach(p -> System.out.println("limit1To10 Nums ="+p));

    }
}

注意這裏 Stream<Integer> streamListData = listData.parallelStream();
輸出結果:

limit1To10 Nums =9
limit1To10 Nums =5
limit1To10 Nums =3
limit1To10 Nums =1
limit1To10 Nums =4
limit1To10 Nums =6
limit1To10 Nums =10
limit1To10 Nums =7
limit1To10 Nums =2
limit1To10 Nums =8

通過對比上面兩個例子的輸出結果,不難發現使用了 parallelStream 的操作,沒有按照順序輸出,因爲是並行運作,因此在處理大量集合時並行處理將非常有幫助。

5. Date Time API 加強對日期與時間的處理

直到 JDK 1.7java.util.Datejava.util.Calendar 這兩個類是處理日期和時間的主要類,但是這兩個類的使用有以下問題:

  1. 線程不安全:java.util.Date 這個類是線程不安全;
  2. 處理麻煩:默認的開始日期從1900年,不支持國際化,不提供時區支持;
  3. 設計不合理:例如 java.utiljava.sql 包中都有日期類,類名卻是一樣的。

JDK 1.8 中的新日期和時間API解決了的以上問題。
java.time 包中包含API中的核心類, 此外,還有其他四個包,使用概率比較少

  • java.time.chrono:提供對不同的日曆系統的訪問
  • java.time.temporal:包括底層框架和擴展特性
  • java.time.format:提供用於打印和解析日期和時間的類
  • java.time.zone:提供對時區及其規則的支持
6. 集合 API 改進
  • Iterator類 forEachRemaining(Consumer action) 在所有元素都已處理完畢或該動作引發異常之前,對每個剩餘元素執行給定操作的默認方法,通過源碼中 action.accept(next()); 其實是在對集合做數據處理的操作,這裏插入了一個指定的動作 action 。
  • Collection類 removeIf(Predicate filter) 刪除滿足 filter 條件的集合中所有元素的方法。
  • Collection類 spliterator() 該方法返回 Spliterator 實例,該實例可用於順序或並行遍歷元素,是爲了並行遍歷元素而設計的一個迭代器。
  • Map類 replaceAll(),compute(),merge()方法。
  • HashMap類 解決Hash衝突的Hash算法的改進,Entry 數據鏈的算法改進。
7. 併發 API 改進

ConcurrentHashMap
JDK1.8 中 針對 ConcurrentHashMap 做了一些改動,下面簡單介紹一下 JDK1.7 ConcurrentHashMap 的實現原理:
在這裏插入圖片描述

將整個hashmap分成幾個小的map,每個segment都是一個鎖;與hashtable相比,這麼設計的目的是對於put, remove等操作,可以減少併發衝突,對不屬於同一個片段的節點可以併發操作。

JDK1.8 做了如下改動:

  1. 取消 Segments 字段,直接採用 transient volatile HashEntry<K,V>[] table 保存數據,採用table數組元素作爲鎖,從而實現了對每一行數據進行加鎖,減少併發衝突的概率。
  2. 將原先table數組+單向鏈表的數據結構,變更爲table數組+單向鏈表+紅黑樹的結構。

Executors
Executors 類新增 newWorkStealingPool() 線程池:使所有可用處理器作爲目標,並行級別創建竊取線程池的方法。

”創建竊取“ 可以這麼簡單的理解:如果有三個線程 x , y , z ,如果 x 創建了3個任務, y 創建了 2 個任務, z 創建了 1 個任務, 假如,z 先 線程執行完任務後,它會主動的去 x 線程中竊取其他的任務進行執行(此時,x 線程尚未執行完成)。

8. Optional 類,解決空指針問題

Optional 類主要解決空指針異常 NullPointerException

Optional 類的使用示例

import java.util.Optional;

public class OptionalDemo {
    public static void main(String[] args) {

        String str = "OptionalDemo";
        // 創建 Optional 
        Optional<String> opt = Optional.ofNullable(str);

        // 獲取 Optional 中的值
        String optValue = opt.get();
    }
   
}

9. 新工具 新的編譯工具

Nashorn引擎jjs
jjs 是一個基於標準 Nashorn 引擎的命令行工具,可以接受 js 源碼並執行。

編寫示例,jjsDemo.js 文件,內容如下:

function fun() { 
     return 1; 
}; 

print( fun() + 1 );

在命令行中輸入執行命令 jjs jjsDemo.js

控制檯輸出結果是:

2

類依賴分析器jdeps

jdeps 是一個命令行工具,它可以展示包層級和類層級的 Java 類依賴關係,以.class文件、目錄或者Jar文件爲輸入,把依賴關係輸出到控制檯。
例如直接在控制檯輸入:jdeps target
此時輸出在控制檯上的內容將會是,目前這個工作目錄直接的 Java 類依賴關係,如下:

iterable.java8 (target)
      -> java.io                                            
      -> java.lang                                          
      -> java.lang.invoke                                   
      -> java.util                                          
      -> java.util.function                                 
      -> java.util.stream   

結論

本文討論了 Java8 一些有趣的新功能和案例,Java 8 使得 Java 平臺發展又前進了一大步。

參考資料

Java 8 新特性

Java 8 Features with Examples (Java 8特性與示例

New Features in Java 8(Java 8的新功能

What’s New in JDK 8 (JDK 8的新增功能

快過年了,今年最大的成就感,就是一直在朝着自己想要的方向前進。

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