jdk13快來了,jdk8的這幾點應該看看!

說明

jdk8雖然出現好久了,可是可能咱們仍是有不少人並不太熟悉,本文主要就是介紹說明一些jdk8相關的內容。html

主要會講解:java

  • lambda表達式
  • 方法引用
  • 默認方法
  • Stream
  • 用Optional取代null
  • 新的日誌和時間
  • CompletableFuture
  • 去除了永久代(PermGen) 被元空間(Metaspace)代替

咱們來看看阿里規範裏面涉及到jdk8相關內容:算法

jdk8開篇

https://www.oracle.com/technetwork/java/javase/8-whats-new-2157071.html編程

主要有:api

1:lambda表達式:一種新的語言特性,可以把函數做爲方法的參數或將代碼做爲數據。lambda表達式使你在表示函數接口(具備單個方法的接口)的實例更加緊湊。數組

2:方法引用 是lambda表達式的一個簡化寫法,所引用的方法實際上是lambda表達式的方法體實現,這樣使代碼更容易閱讀數據結構

3:默認方法:Java 8引入default method,或者叫virtual extension method,目的是爲了讓接口能夠過後添加新方法而無需強迫全部實現該接口的類都提供新方法的實現。也就是說它的主要使用場景可能會涉及代碼演進。多線程

4: Stream 不是 集合元素,也不是數據結構,它至關於一個 高級版本的 Iterator,不能夠重複遍歷裏面的數據,像水同樣,流過了就一去不復返。它和普通的 Iterator 不一樣的是,它能夠並行遍歷,普通的 Iterator 只能是串行,在一個線程中執行。操做包括:中間操做 和 最終操做(只能操做一遍) 串行流操做在一個線程中依次完成。並行流在多個線程中完成,主要利用了 JDK7 的 Fork/Join 框架來拆分任務和加速處理。相比串行流,並行流能夠很大程度提升程序的效率併發

5:用Optional取代nulloracle

6:新的日誌和時間,可使用Instant代替Date LocalDateTime代替Calendar DateTimeFormatter代替SimpleDateFormat

7:CompletableFuture:CompletableFuture提供了很是強大的Future的擴展功能,能夠幫助咱們簡化異步編程的複雜性,而且提供了函數式編程的能力,能夠經過回調的方式處理計算結果,也提供了轉換和組合 CompletableFuture 的方法。

8:去除了永久代(PermGen) 被元空間(Metaspace)代替 配置:-XX:MetaspaceSize=8m -XX:MaxMetaspaceSize=80m 代替 -XX:PermSize=10m -XX:MaxPermSize=10m

lambda

JDK8最大的特性應該非lambda莫屬!

IDEA工具自動提示:

lambda語法結構 : 完整的Lambda表達式由三部分組成:參數列表、箭頭、聲明語句;

(Type1 param1, Type2 param2, ..., TypeN paramN) -> {  statment1;  statment2;  //.............  return statmentM;}

絕大多數狀況,編譯器均可以從上下文環境中推斷出lambda表達式的參數類型,因此參數能夠省略:

(param1,param2, ..., paramN) -> {  statment1;  statment2;  //.............  return statmentM;}

當lambda表達式的參數個數只有一個,能夠省略小括號:

param1 -> {  statment1;  statment2;  //.............  return statmentM;}

當lambda表達式只包含一條語句時,能夠省略大括號、return和語句結尾的分號:

param1 -> statment

在那裏以及如何使用Lambda????

你能夠在函數式接口上面使用Lambda表達式。

備註: JDK定義了不少如今的函數接口,實際本身也能夠定義接口去作爲表達式的返回,只是大多數狀況下JDK定義的直接拿來就能夠用了。

Java SE 7中已經存在的函數式接口:

除此以外,Java SE 8中增長了一個新的包:java.util.function,它裏面包含了經常使用的函數式接口,例如:

  • Predicate<T>——接收T對象並返回boolean
  • Consumer<T>——接收T對象,不返回值
  • Function<T, R>——接收T對象,返回R對象
  • Supplier<T>——提供T對象(例如工廠),不接收值

隨便看幾個:

默認方法

Java 8 引入了新的語言特性——默認方法(Default Methods)。

Default methods enable new functionality to be added to the interfaces of libraries and ensure binary compatibility with code written for older versions of those interfaces.

默認方法容許您添加新的功能到現有庫的接口中,並能確保與採用舊版本接口編寫的代碼的二進制兼容性。

默認方法是在接口中的方法簽名前加上了 default 關鍵字的實現方法。

爲何要有默認方法

在 java 8 以前,接口與其實現類之間的 耦合度 過高了(tightly coupled),當須要爲一個接口添加方法時,全部的實現類都必須隨之修改。默認方法解決了這個問題,它能夠爲接口添加新的方法,而不會破壞已有的接口的實現。這在 lambda 表達式做爲 java 8 語言的重要特性而出現之際,爲升級舊接口且保持向後兼容(backward compatibility)提供了途徑。

這個 forEach 方法是 jdk 1.8 新增的接口默認方法,正是由於有了默認方法的引入,纔不會由於 Iterable 接口中添加了 forEach 方法就須要修改全部 Iterable 接口的實現類。

方法引用(Method references)

若是一個Lambda表達式僅僅是調用方法的狀況,那麼就能夠用方法引用來完成,這種狀況下使用方法引用代碼更易讀。

方法引用語法:

目標引用放在分隔符::前,方法的名稱放在後面。

names2.forEach(System.out::println);//1
names2.forEach(s->System.out.println(s));//2

第二行代碼的lambda表達式僅僅就是調用方法,調用的System.out的println方法,因此能夠用方法引用寫成System.out::println便可。

方法引用的種類(Kinds of method references)

方法引用有不少種,它們的語法以下:

  • 靜態方法引用:ClassName::methodName
  • 實例上的實例方法引用:instanceReference::methodName
  • 父類的實例方法引用:super::methodName
  • 類型上的實例方法引用:ClassName::methodName

**備註:**String::toString 等價於lambda表達式 (s) -> s.toString() 這裏不太容易理解,實例方法要經過對象來調用,方法引用對應Lambda,Lambda的第一個參數會成爲調用實例方法的對象。

  • 構造方法引用:Class::new
  • 數組構造方法引用:TypeName[]::new

我的理解:方法引用,說白了,用更好,不用也能夠,若是能夠儘可能用!!!

Stream

Java 8 中的 Stream 是對集合(Collection)對象功能的加強,它專一於對集合對象進行各類很是便利、高效的聚合操做(aggregate operation),或者大批量數據操做 (bulk data operation)。Stream API 藉助於一樣新出現的 Lambda 表達式,極大的提升編程效率和程序可讀性。同時它提供串行和並行兩種模式進行匯聚操做,併發模式可以充分利用多核處理器的優點,使用 fork/join 並行方式來拆分任務和加速處理過程。一般編寫並行代碼很難並且容易出錯, 但使用 Stream API 無需編寫一行多線程的代碼,就能夠很方便地寫出高性能的併發程序。

  • Stream 不是集合元素,它不是數據結構並不保存數據,它是有關算法和計算的,它更像一個高級版本的 Iterator。
  • Stream 就如同一個迭代器(Iterator),單向,不可往復,數據只能遍歷一次,遍歷過一次後即用盡了,就比如流水從面前流過,一去不復返。
  • 和迭代器又不一樣的是,Stream 能夠並行化操做,迭代器只能命令式地、串行化操做。

對stream的操做分爲三類。

  1. 建立stream
  2. 中間操做(intermediate operations)【沒有終止操做是不會執行的】
  3. 終止操做(terminal operations):

中間操做會返回另外一個流。能夠用鏈式編程.的形式繼續調用。在沒有終止操做的時候,中間操做是不會執行的。

終止操做不會返回流了,而是返回結果(好比返回void-僅僅System.out輸出,好比返回總數 int,返回一個集合list等等)

例如:

流的建立

3種方式建立流,普通流調用

  • 經過Stream接口的靜態工廠方法

  • 經過Arrays方法

  • 經過Collection接口的默認方法

//經過Stream接口的靜態工廠方法
Stream stream = Stream.of("hello", "world", "hello world");

String[] strArray = new String[]{"hello", "world", "hello world"};
//經過Stream接口的靜態工廠方法
Stream stream1 = Stream.of(strArray);

//經過Arrays方法
Stream stream2 = Arrays.stream(strArray);

List<String> list = Arrays.asList(strArray);
//經過Collection接口的默認方法
Stream stream3 = list.stream();

本質都是StreamSupport.stream。

經過Collection接口的默認方法獲取並行流。

或者經過stream流調用parallel獲取並行流

只須要對並行流調用sequential方法就能夠把它變成順序流

中間操做

終止操做

並行流

能夠經過對收集源調用parallelStream方法來把集合轉換爲並行流。並行流就是一個把內容分紅多個數據 塊,並用不一樣的線程分別處理每一個數據塊的流。這樣一來,你就能夠自動把給定操做的工做負荷分配給多核處理器的全部內核,讓它們都忙起來。

並行流用的線程是從哪兒來的?有多少個?怎麼自定義這個過程呢?

並行流內部使用了默認的ForkJoinPool,它默認的線程數量就是你的處理器數量,這個值是由 Runtime.getRuntime().available Processors()獲得的。可是你能夠經過系統屬性 java.util.concurrent.ForkJoinPool.common. parallelism來改變線程池大小,以下所示: System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism","12"); 這是一個全局設置,所以它將影響代碼中全部的並行流。反過來講,目前還沒法專爲某個 並行流指定這個值。通常而言,讓ForkJoinPool的大小等於處理器數量是個不錯的默認值, 除非你有很好的理由,不然咱們強烈建議你不要修改它

測試並行流和順序流速度

//Sequential Sort, 採用順序流進行排序
    @Test
    public void sequentialSort(){
        long t0 = System.nanoTime();

        long count = values.stream().sorted().count();
        System.err.println("count = " + count);

        long t1 = System.nanoTime();

        long millis  = TimeUnit.NANOSECONDS.toMillis(t1 - t0);
        System.out.println(String.format("sequential sort took: %d ms", millis));
        //sequential sort took: 1932 ms

    }

    //parallel Sort, 採用並行流進行排序
    @Test
    public void parallelSort(){
        long t0 = System.nanoTime();

        long count = values.parallelStream().sorted().count();
        System.err.println("count = " + count);

        long t1 = System.nanoTime();

        long millis  = TimeUnit.NANOSECONDS.toMillis(t1 - t0);
        System.out.println(String.format("parallel sort took: %d ms", millis));
        //parallel sort took: 1373 ms 並行排序所花費的時間大約是順序排序的一半。
    }

錯誤使用流

class Accumlator{
    public long total = 0;

    public void add(long value) {
        total += value;
    }
}


public class ParallelTest {
    public static void main(String[] args) {
        //錯誤使用並行流示例
        System.out.println("SideEffect parallel sum done in :" + measureSumPerf(ParallelTest::sideEffectParallelSum, 1_000_000_0) + "mesecs");
        System.out.println("=================");
        //正確應該這樣的
        System.out.println("SideEffect  sum done in :" + measureSumPerf(ParallelTest::sideEffectSum, 1_000_000_0) + "mesecs");
    }

    //錯誤使用並行流
    public static long sideEffectParallelSum(long n) {
        Accumlator accumlator = new Accumlator();
        LongStream.rangeClosed(1, n).parallel().forEach(accumlator::add);
        return accumlator.total;
    }

    //正確使用流
    public static long sideEffectSum(long n) {
        Accumlator accumlator = new Accumlator();
        LongStream.rangeClosed(1, n).forEach(accumlator::add);
        return accumlator.total;
    }

    //定義測試函數
    public static long measureSumPerf(Function<Long, Long> adder, long n) {
        long fastest = Long.MAX_VALUE;
        //迭代10次
        for (int i = 0; i < 2; i++) {
            long start=System.nanoTime();
            long sum = adder.apply(n);
            long duration=(System.nanoTime()-start)/1_000_000;
            System.out.println("Result: " + sum);
            //取最小值
            if (duration < fastest) {
                fastest = duration;
            }
        }
        return fastest;
    }

}

本質問題在於total += value;它不是原子操做,並行調用的時候它會改變多個線程共享的對象的可變狀態,從而致使錯誤,在使用並行流須要避免這類問題發生!

思考: 什麼狀況結果正常,可是並行流比順序流慢的狀況呢???

並行流中更新共享變量,若是你加入了同步,極可能會發現線程競爭抵消了並行帶來的性能提高!

特別是limit和findFirst等依賴於元素順序的操做,它們在並行流上執行的代價很是大

對於較小的數據量,選擇並行流幾乎歷來都不是一個好的決定。並行處理少數幾個元素的好處還抵不上並行化形成的額外開銷。

**備註:**sort或distinct等操做接受一個流,再生成一個流(中間操做),從流中排序和刪除重複項時都須要知道全部集合數據,若是集合數據很大可能會有問題(若是數據大,都放內存,內存不夠就會OOM了)。

使用並行流仍是順序流都應該應該測試,以及壓測,若是在並行流正常的狀況下,效率有提高就選擇並行流,若是順序流快就選擇順序流。

CompletableFuture異步函數式編程

引入CompletableFuture緣由

Future模式的缺點

  • Future雖然能夠實現獲取異步執行結果的需求,可是它沒有提供通知的機制,咱們沒法得知Future何時完成
  • 要麼使用阻塞,在future.get()的地方等待future返回的結果,這時又變成同步操做。要麼使用isDone()輪詢地判斷Future是否完成,這樣會耗費CPU的資源。

Future 接口的侷限性

future接口能夠構建異步應用,但依然有其侷限性。**它很難直接表述多個Future 結果之間的依賴性。**實際開發中,咱們常常須要達成如下目的:

  • 將兩個異步計算合併爲一個——這兩個異步計算之間相互獨立,同時第二個又依賴於第 一個的結果。
  • 等待 Future 集合中的全部任務都完成。
  • 僅等待 Future 集合中最快結束的任務完成(有可能由於它們試圖經過不一樣的方式計算同 一個值),並返回它的結果。
  • 經過編程方式完成一個 Future 任務的執行(即以手工設定異步操做結果的方式)。
  • 應對 Future 的完成事件(即當 Future 的完成事件發生時會收到通知,並能使用 Future 計算的結果進行下一步的操做,不僅是簡單地阻塞等待操做的結果)

新的CompletableFuture將使得這些成爲可能。

CompletableFuture提供了四個靜態方法用來建立CompletableFuture對象:

方法入參和返回值有所區別。

裏面有很是多的方法,返回爲CompletableFuture以後能夠用鏈式編程.的形式繼續調用,最後調用一個不是返回CompletableFuture的介紹,和流式操做裏面的中間操做-終止操做。

日期

/**
     * 可使用Instant代替Date
     * LocalDateTime代替Calendar
     * DateTimeFormatter代替SimpleDateFormat
     */

    public static void main(String args[]) {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        LocalDateTime now = LocalDateTime.now();
        System.out.println(now.format(formatter));

        //10分鐘前
        String d1 = now.minusMinutes(10).format(formatter);
        //10分鐘後
        String d2 = now.plusMinutes(10).format(formatter);

        System.out.println(d1);
        System.out.println(d2);


        LocalDateTime t5 = LocalDateTime.parse("2019-01-01 00:00:00", formatter);

        System.out.println(t5.format(formatter));


    }

JVM方面改變

去除了永久代(PermGen) 被元空間(Metaspace)代替 配置:-XX:MetaspaceSize=8m -XX:MaxMetaspaceSize=80m 代替 -XX:PermSize=10m -XX:MaxPermSize=10m

用Optional取代null

Optional對象建立

一、 建立空對象

Optional<String> optStr = Optional.empty();

上面的示例代碼調用empty()方法建立了一個空的Optional<String>對象型。

二、**建立對象:不容許爲空 ** Optional提供了方法of()用於建立非空對象,該方法要求傳入的參數不能爲空,不然拋NullPointException,示例以下:

Optional<String> optStr = Optional.of(str);  // 當str爲null的時候,將拋出NullPointException

三、**建立對象:容許爲空 ** 若是不能肯定傳入的參數是否存在null值的可能性,則能夠用Optional的ofNullable()方法建立對象,若是入參爲null,則建立一個空對象。示例以下:

Optional<String> optStr = Optional.ofNullable(str);  // 若是str是null,則建立一個空對象

經常使用方法

String str = null;

len = Optional.ofNullable(str).map(String::length).orElse(0); //不會報NullPointerException

**若是讀完以爲有收穫的話,歡迎點贊、關注、加公衆號 [匠心零度] ,查閱更多精彩歷史!!! **

原文出處:https://www.cnblogs.com/jiangxinlingdu/p/11476892.html

相關文章
相關標籤/搜索