Java8 函數(shù)式編程

Java 8 函數(shù)式編程

本次主題主要介紹什么是函數(shù)式編程,其主要特點, 以及它在 Java8 中是怎么體現(xiàn)的.


函數(shù)式編程這個思維早在1958年就被提出來的了,在60年后的今天重新被提出來,且越來越流行,其主要原因是 CPU 的時鐘頻率發(fā)展停滯不前,目前徘徊在4GHZ左右糯笙。

CPU 的時鐘頻率越快,則運行速度越快,但目前的工藝技術(shù)限制了 CPU 頻率的進一步提升,主流的芯片廠商目前都轉(zhuǎn)向了多核處理器這個架構(gòu)方向,所以我們編程不能再依賴時鐘頻率提高現(xiàn)有代碼的計算能力,需要充分利用現(xiàn)在 CPU 架構(gòu),而這唯一的辦法就是編寫并行化的代碼.

“學(xué)習(xí)一種全新的編程范式仓洼,困難并不在于掌握新的語言生年。
畢竟能拿起這本書的讀者邓夕,學(xué)過的編程語言少說也有一籮筐——語法不過是些小細節(jié)罷了。
真正考驗人的,是怎么學(xué)會用另一種方式去思考 。”

摘錄來自: [美] 福特(Neal Ford). “函數(shù)式編程思維 (圖靈程序設(shè)計叢書)”苇羡。 

函數(shù)式編程思想,可以編寫出簡潔,高效的并行代碼,這也就是為什么現(xiàn)在又將函數(shù)式編程思維提出來,且越來越成為一種趨勢的原因.

什么是函數(shù)式編程?

計算機編程語言的分類:

機器語言

匯編語言

高級語言

轉(zhuǎn)換方式 客觀描述 編程范型
編譯型 面向過程 命令式
解釋型 面向?qū)ο?/td> 函數(shù)式

轉(zhuǎn)換方式

編譯型,即事先將程序翻譯成了 CPU 指令,只能運行在特地的環(huán)境里(CPU, 架構(gòu)等,例如 C)

解釋性,即邊解析程序邊翻譯成 CPU 指令,一般和平臺無關(guān)(例如 Java).

說通俗點, 編譯型語言即把東西煮熟了,可以直接開吃,而解釋型語言是火鍋,要邊煮邊吃.

所以編譯型語言吃的快,解釋性語言吃的慢.

_002.jpg
_003.jpg

Java 雖然會將代碼編程成 class 文件,但 class 文件本質(zhì)上是字節(jié)碼,并不是 CPU 指令,所以不是編譯型語言.

客觀描述

說面向過程和面向?qū)ο?我們先來說說寫史書的兩種方式:編年體和紀傳體.


編年體是按照時間來寫,比如說某一年發(fā)生了什么大事鼻弧、誰誰干了什么设江,第二年又有什么大事、誰誰干了什么攘轩,這么寫叉存。

紀傳體就是為某人立傳的形式來寫。以一個人為主線度帮。比如說歼捏,某某,生于多少年笨篷,哪一年干了什么瞳秽,哪一年又干了些什么,一直寫下去率翅。而且并不一定嚴格按照時間為順序,寫書的人可能會采用倒敘等方式敘述.

_005.jpg
_004.jpg

而編年體和紀傳體就是對應(yīng)著面向過程和面向?qū)ο?面向過程注重的是步驟,而面向?qū)ο髣t以對象為主,著重于解決問題的思路

編程范式

命令式編程是面向計算機硬件的抽象练俐,有變量(對應(yīng)著存儲單元),賦值語句(獲取安聘,存儲指令)痰洒,表達式(內(nèi)存引用和算術(shù)運算)和控制語句(跳轉(zhuǎn)指令)瓢棒,一句話浴韭,命令式程序就是一個馮諾依曼機的指令序列。

而函數(shù)式編程是面向數(shù)學(xué)的抽象脯宿,將計算描述為一種表達式求值念颈,一句話,函數(shù)式程序就是一個表達式连霉。

這里只要的區(qū)別是在寫代碼的過程的思維,而不是編譯后的結(jié)果.

可能一個函數(shù)式語言,經(jīng)過編譯后最后成了一條條 CPU 指令,但是并不能說明這個語言不是函數(shù)式語言.

函數(shù)式編程

以 Java 為例:

String getName(){
    return name;
}


上述代碼, getName 我們通常稱之為 "方法", 也可以稱之為 "函數(shù)".

方法與函數(shù)到底有什么區(qū)別呢?

兩個稱呼不同其實編程范式思想.
方法,即注重解決問題的步驟,過程. 函數(shù),則注重類型之間的代數(shù)關(guān)系


這里所說的函數(shù),指的是數(shù)學(xué)上的函數(shù).

讓我們先回顧一下中學(xué)數(shù)學(xué),函數(shù)是怎么定義的:

_001.jpg

函數(shù)的定義:給定一個數(shù)集A榴芳,假設(shè)其中的元素為x∥嗣遥現(xiàn)對A中的元素x施加對應(yīng)法則f,記作f(x)窟感,得到另一數(shù)集B讨彼。假設(shè)B中的元素為y。則y與x之間的等量關(guān)系可以用y=f(x)表示柿祈。我們把這個關(guān)系式就叫函數(shù)關(guān)系式哈误,簡稱函數(shù)。函數(shù)概念含有三個要素:定義域A躏嚎、值域C和對應(yīng)法則f蜜自。其中核心是對應(yīng)法則f,它是函數(shù)關(guān)系的本質(zhì)特征卢佣。

其中這個法則 f, 在函數(shù)式編程中就稱之為 <b>函數(shù)</b>,所謂的函數(shù)式編程,即編寫代碼的時候,注重的是類型之間的代數(shù)關(guān)系 fun.

舉個例子:

在學(xué)習(xí)編程的時候,有一個流傳了很廣的??→ 交換 A,B 兩數(shù)

//方法思想
void change(int a,int b){
    int c = a;
    a = b;
    b = c;
    System.out.println("a: " + a + "   b:" + b); 
}
//函數(shù)思想
void change(int a,int b){
    System.out.println("a: " + b + "   b:" + a);
}
_006.jpg

這本來是我上編程課,老師講的一個笑話,說有一個學(xué)生偷懶,直接將 a,b 交換輸出了.
當時覺得那個人真是腦洞大開,而寫代碼應(yīng)該追求簡單明了,這種偷懶,其實也應(yīng)該得到鼓勵和支持.

交換A,B 兩數(shù),從本質(zhì)上來講,是闡述了 A,B 兩數(shù)與結(jié)果之間是一個互換的行為,而具體怎么交換,應(yīng)該有什么步驟, 不同的語言和平臺有不同的方法, 但最終描述的都是這樣的一個本質(zhì).

而函數(shù)式編程,其實就是追求一種<b>"簡單的,高效的,類型之間的關(guān)系"</b>.

這樣的思想更加符合我們?nèi)说乃季S,更加的抽象.

定義:

函數(shù)式編程,實際上是一種編程范式,區(qū)別于命令式注重解決問題的步驟,函數(shù)式注重的數(shù)據(jù)之間的關(guān)系 --- 即 f(x).

從上面的介紹可知,函數(shù)式編程實際上是在寫代碼的時候的一種編程思想,無關(guān)編譯后具體是怎么處理和執(zhí)行的,例如現(xiàn)在非持剀火的 Kotlin, 作為一種函數(shù)式編程語言,可以將它編譯成 class 字節(jié)碼,從編譯后的字節(jié)碼來看,和 Java8 之前的編譯出來的代碼并沒有本質(zhì)的區(qū)別.

函數(shù)式編程的特點

  1. 代碼簡潔,開發(fā)快速

    函數(shù)式編程將大量重復(fù)的的通用操作寫成函數(shù)虚茶,減少了代碼的重復(fù)戈鲁,因此代碼少, 開發(fā)速度快, 而 Kotlin 之所以比 Java 更簡潔, 這也是其中一個重要的原因.

  2. 更接近自然語言(人類的語言),易于理解

    前面的例子,已經(jīng)說明,函數(shù)式編程的思維更接近人類的思維,所以更易被我們理解.
    下面會用 Java8 舉例說明

  3. 更方便的代碼管理,易于"并發(fā)編程"

    函數(shù)式編程更為抽象, 更通用的抽象函數(shù)能更加解耦, 模塊化管理更加方便.

    無論是 Java 亦或是其他語言,在并發(fā)這塊,代碼編寫和管理都是一個難點.

    函數(shù)式編程不依賴嘹叫、也不會改變外界的狀態(tài)荞彼,只要給定輸入?yún)?shù),返回的結(jié)果必定相同待笑。因此鸣皂,每一個函數(shù)都可以被看做獨立單元,也不用擔心死鎖,同步,以及內(nèi)存可見等各種頭疼的多線程問題,這將非常有利于進行單元測試和除錯暮蹂,以及模塊化,解耦等等寞缝。

Java8的函數(shù)式編程

_007.jpg

Lambda 表達式

Lambda, 數(shù)學(xué)符號為 λ, 我們上學(xué)時候經(jīng)常使用的符號還有alpha--α, beta--β 等等, 使用 λ 作為函數(shù)式編程的符號,是因為 <b>λ演算</b>:


從理論上說,函數(shù)式語言也不是通過馮諾伊曼體系結(jié)構(gòu)的機器上運行的仰泻,而是通過λ演算來運行的荆陆,就是通過變量替換的方式進行,變量替換為其值或表達式集侯,函數(shù)也替換為其表達式被啼,并根據(jù)運算符進行計算。λ演算是圖靈完全(Turing completeness)的棠枉,但是大多數(shù)情況浓体,函數(shù)式程序還是被編譯成(馮諾依曼機的)機器語言的指令執(zhí)行的。

通俗來說,函數(shù)式思想的本質(zhì)是 λ演算,但這個思想具體到了編程,還是使用機器語言去執(zhí)行的,所以和純粹的函數(shù)式思想有一點點區(qū)別,而不同的函數(shù)式編程語言,在具體的實現(xiàn)上也不盡相同.

_008.jpg

Java8 中的 Lambda 表達式 表現(xiàn)為高階函數(shù),即將函數(shù)作為參數(shù)傳入.下面來認識一下 Java8 中的 Lambda 表達式:

//0, lambda 表達式的右邊是一個閉包,應(yīng)該使用{}括起來,這是 lambda 表達式最完整的表達
Consumer<String> consumer3 = (String it) -> {
    System.out.println(it);
};


//1, 0 個參數(shù), run() 方法演變成 lambda 表達式
Runnable runnable = () -> System.out.println("123");


//2, 1個參數(shù),傳如 it,假設(shè)這個類型可以推斷出來,則可以省略
Consumer<String> consumer = it -> System.out.println(it);


//3, 指出了 it 的具體類型為 String
Consumer<String> consumer2 = (String it) -> System.out.println(i);



//4, 多條語言,不能省略右邊的大括號
Consumer<String> consumer4 = (String it) -> {//省略大括號編譯不能通過
    System.out.println(it);
    System.out.println("other code: " + it);
    //other code
    //
};


//5, 多參數(shù)
BinaryOperator<String> operator = (x, y) -> {
    return x + y;
};


//6:方法引用, 0,2,3都可以轉(zhuǎn)為方法引用, 1不行因為輸出了字符串 "123", 4是因為有多條語句
Consumer<String> consumer5 = System.out::println;
Consumer<String> consumer6 = Person::getName;
Consumer<String> consumer7 = Person::new;                                           

類庫

認識了 Lambda 表達式之后,我們來使用它,舉個??

Java 編程中,經(jīng)常使用 CallBack 來進行回調(diào),通常代碼如下:

public static class Person {
    private NameChangeListener listener;
    public void setListener(NameChangeListener listener) {
        this.listener = listener;
    }
    public static interface NameChangeListener {
        void change(String name);
    }
}
public static class God {
    private NameChangeListener listener;
    public void setListener(NameChangeListener listener) {
        this.listener = listener;
    }
    public static interface NameChangeListener {
        void change(String name);
    }
}

public static void main(String[] args) {
    Person person = new Person();
    person.setListener(new Person.NameChangeListener() {
        @Override
        public void change(String name) {
            System.out.print("new name: " + name);
        }
    });
    God god = new God();
    god.setListener(new God.NameChangeListener() {
        @Override
        public void change(String name) {
            System.out.print("new name: " + name);
        }
    });
}

上面兩個類, Person 和 God 都有一個回調(diào),且回調(diào)的簽名是一模一樣的,但是不能將 God 的回調(diào)實例傳遞給 Person:

 Person person = new Person();
    person.setListener(new God.NameChangeListener() {//報錯,編譯無法通過
        @Override
        public void change(String name) {
            System.out.print("new name: " + name);
        }
    });

盡管兩個回調(diào)的簽名一模一樣的,但是假設(shè)這兩個類是來自不同的類庫, Person 是無法訪問到 God 中的回調(diào)接口,也就無法使用 God 中的回調(diào)替換, 故無法編譯通過.

現(xiàn)在將這兩個回調(diào)統(tǒng)一,抽取出相同的簽名,如下:

interface NameChangeListener {
    void change(String name);
}

//更為通用的,假設(shè)參數(shù)不是一個 String, 而是任意的類型,使用泛型可以這么寫
interface Consumer<T> {
    void change(T var);
}

上面的 Consumer 這個接口, 只有一個方法,接受一個 T, 返回一個 void, 假設(shè)我們能告訴編譯器,任何符合這樣簽名的,都可以替換掉,那豈不是美滋滋?

_012.jpg

<b>Lambda 本質(zhì)上就是將一個通用的函數(shù)接口替換掉和它簽名一模一樣的其他類或接口</b>

使用 Lambda 替換 Person 的匿名內(nèi)部類:

Person person = new Person();
person.setListener(name -> System.out.print("new name: " + name));

根據(jù)之前的 lambda 表達式的介紹,上面這段表達式的是意思是,傳入一個參數(shù),這個參數(shù),從類型推斷上來講,它是 String 類型, 且執(zhí)行了一條打印語言,并且沒有返回值.
這個函數(shù)從簽名上來講,和 Person.NameChangeListener 一模一樣,故在 Java8中允許使用這個 lambda 表達式替換掉前面的匿名內(nèi)部類.

上面這個表達式,其本質(zhì)上是使用 Java8自帶的一個函數(shù)接口,稱之為類庫,我們看一下它的源碼:

_009.jpg

可以看到,這個函數(shù)接口, Consumer 和我們上面抽象出來的函數(shù)接口一樣,多了一個默認方法而已,這個默認方法是 java8 接口新增的一個特性,是為了兼容舊接口且保持向后兼容(backward compatibility)提供了途徑.

這里最重要的不是默認方法,而是那個 <b>@FunctionalInterface</b> 注解,表示這個接口是一個函數(shù)接口,可以使用 lambda 表達式,關(guān)于這樣的函數(shù)接口還有很多,都在

package java.util.function;

這個包下面.其實我們在接觸 RxJava 的時候,就已經(jīng)學(xué)到使用到了這些函數(shù)接口,常用的函數(shù)接口非常多,以至于幾乎無法一一記住.

但一般無需刻意去記住這些函數(shù)接口,我們在使用 lambda 表達式的時候無需刻意去想需要使用哪個函數(shù)接口,編譯器會自動匹配. 只有我們在閱讀類庫,或者設(shè)計類庫的時候,可能需要去理解和使用到這些函數(shù)接口.

如果是 Java8 中的 lambda 表達式"僅僅"是一種"語法糖",而 Stream 的出現(xiàn)則是 Java 8 的最大亮點.

它與 java.io 包里的 InputStream 和 OutputStream 是完全不同的概念辈讶。它也不同于 StAX 對 XML 解析的 Stream.


Java 8 中的 Stream 是對集合(Collection)對象功能的增強命浴,它專注于對集合對象進行各種非常便利、高效的聚合操作(aggregate operation),或者大批量數(shù)據(jù)操作 (bulk data operation)生闲。Stream API 借助于同樣新出現(xiàn)的 Lambda 表達式媳溺,極大的提高編程效率和程序可讀性。同時它提供串行和并行兩種模式進行匯聚操作碍讯,并發(fā)模式能夠充分利用多核處理器的優(yōu)勢悬蔽,使用 fork/join 并行方式來拆分任務(wù)和加速處理過程。通常編寫并行代碼很難而且容易出錯, 但使用 Stream API 無需編寫一行多線程的代碼捉兴,就可以很方便地寫出高性能的并發(fā)程序屯阀。所以說,Java 8 中首次出現(xiàn)的 java.util.stream 是一個函數(shù)式語言+多核時代綜合影響的產(chǎn)物轴术。

_010.jpg



集合都實現(xiàn)了 Collection 接口,在 java8 以后,加上了一個 stream() 的默認方法, 用于使用 流 功能,舉個??,計算集合中的所有大于5的數(shù)字的個數(shù):

    /**
     * 計算大于5的個數(shù)
     */
    public static long addUp(List<Integer> data) {
        long count = 0;
        for (int i = 0; i < data.size(); i++) {
            if (data.get(i) > 5) {
                count++;
            }
        }
        return count;
    }

    /**
     * 計算大于5的個數(shù)
     */
    public static long addUp8(List<Integer> data) {
        return data.stream().filter(it -> it > 5).count();
    }


首先會看我們的問題: 計算集合中的所有大于5的數(shù)字的個數(shù)


按照數(shù)學(xué)思維,我們應(yīng)該先將所有大于5的數(shù)字找出來,然后算一共有幾個.


再看上述流的例子,首先 data 通過 stream() 獲得到了集合的流,然后通過 filter() 方法篩選出了大于5的數(shù)字(其中篩選代碼使用了 lambda 表達式 表達),最后通過 count() 方法計數(shù).

根據(jù)函數(shù)式編程的思想,流符合對簡潔的要求,符合人的思維.

Stream 接口中定義了很多 filter, count 等方法, 這些方法的參數(shù)都是前文中所提到到 <b>函數(shù)式接口</b> 風(fēng)格. 這些方法可以按照兩個種類來區(qū)分: 惰性求值和及時求值.

上文中, 假設(shè)將流的代碼改為下面的例子:

    /**
     * 計算大于5的個數(shù)
     */
    public static long addUp8(List<Integer> data) {
        Stream<Integer> stream = data.stream().filter(it -> {
            System.out.println("456");
            return it > 5;
        });
        System.out.println("123");

        return stream.count();
    }

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

結(jié)果:

123
456
456
.......

從結(jié)果中可以看到, 456的代碼盡管是在 123的之前,但是因為 filter() 屬于惰性求值,只有真正的執(zhí)行到它的時候它才會運行.

<b>區(qū)分 Stream 接中的方法是及時求值,還是惰性求值,只要看它的返回值,如果不是一個 Stream, 那就是及時求值.</b>

對 RxJava 比較熟悉的同學(xué)就會發(fā)現(xiàn)了, 流和 RxJava 很像,實際上他們的操作符基本上都是一樣的,編程思想也是類似,都可以定義為一種函數(shù)式編程.

Stream 是為構(gòu)建內(nèi)存中集合的計算流程而設(shè)計的,而 RxJava 則是為了組合異步和基于事件的系統(tǒng)流程而設(shè)計的. Stream 沒有取數(shù)據(jù),而是把數(shù)據(jù)放進去, Stream 是為了計算最終的結(jié)果,而 RxJava 在線程模型上則像是 CompletableFuture, 為了得到一個響應(yīng)的結(jié)果,故RxJava 也叫響應(yīng)式編程.

數(shù)據(jù)并行化

上文流的例子,很好的體現(xiàn)了函數(shù)式編程中的簡潔,接近人的思維,現(xiàn)在介紹易于編寫并發(fā)這塊,下文集合串行并行對比:

    public static void main(String[] args) {
        List<Integer> integerList = new ArrayList<>();
        for (int i = 0; i < 20; i++) {
            integerList.add(i);
        }
        
        //串行
        long b = System.currentTimeMillis();
        System.out.println("start...");
        long a = integerList.stream().map(it -> {
            try {
                TimeUnit.MILLISECONDS.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return it + 1;
        }).count();
        System.out.println("end: " + (System.currentTimeMillis() - b));


            //并行,關(guān)鍵字: parallelStream()
        long c = System.currentTimeMillis();
        System.out.println("\n\n");
        System.out.println("start...");
        long d = integerList.parallelStream().map(it -> {
            try {
                TimeUnit.MILLISECONDS.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return it + 1;
        }).count();
        System.out.println("end: " + (System.currentTimeMillis() - c));
    }

結(jié)果:

start...
end: 6124


start...
end: 1513
_011.jpg

就這么簡單, Java8 的流使用 parallelStream()方法能很方便的進行并行操作,其本質(zhì)上是使用Fork/Join框架實現(xiàn)的(詳情可見), 這個框架自 Java7 引入,能將一個大任務(wù)切割成若干個小任務(wù)并行執(zhí)行

Java8 數(shù)組也有并行化操作,例如我們經(jīng)常使用的數(shù)組初始化:

public static void main(String[] args) {
    long a = System.currentTimeMillis();
    System.out.print("\n\nstart...");
    String[] strings = new String[20];
    for (int i = 0; i < strings.length; i++) {
        try {
            TimeUnit.MILLISECONDS.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        strings[i] = i + "";
    }
    System.out.print("end: " + (System.currentTimeMillis() - a));
    long b = System.currentTimeMillis();
    System.out.print("\n\nstart...");
    String[] strings2 = new String[20];
    Arrays.parallelSetAll(strings2, i -> {
        try {
            TimeUnit.MILLISECONDS.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return i + "";
    });
    System.out.print("end: " + (System.currentTimeMillis() - b));
}

結(jié)果:

start...end: 6059

start...end: 1562

使用 Java8 函數(shù)式編程注意事項

Java 作為一門語言,在很多方面都經(jīng)過了時間的考驗.問題在于,這么多年來, Java 并沒有緊跟時代向前演進, 落了一個保守的名聲,而 Java8的出現(xiàn)則是一個積極的型號,相對于6,7而言,它正在大步前進,同時它的改進也遇到了一些問題.

lambda 表達式可引用外部變量

根據(jù)之前對函數(shù)式思想的介紹, 函數(shù)式是不修改外部變量,這樣更有利于它的編程和測試,更純粹和簡潔,但Java8 中, lambda 表達式還是可以引用外部變量:

    //全局變量
    String a;

    public void testLambda() {
        Consumer<String> consumer = it -> {
            a = it;
        };
    }

    public void testLambda2() {
        String b = "123";
        Consumer<String> consumer = it -> {
            System.out.println(b);
        };
        //如果修改 b 則無法編譯通過
//        b = "456";
    }

可以看到 lambda 表達是仍然可以引用全局變量,這對函數(shù)式思想來說,不是一個好習(xí)慣,所以我們在編寫代碼的時候要避免使用這種情況.

在局部變量的時候,要求這個變量必須是final,或是一個既定的 final 值,即沒有地方會修改到它.

上面例子中,假設(shè)不注釋掉 b = "456", 則編譯無法通過, 因為 Java8的設(shè)計是函數(shù)式編程,lambda 是f(X)函數(shù)關(guān)系,它是對值求關(guān)系,不是對變量求關(guān)系,假設(shè)變量是final,或是一個既定的 final 值,則 表達式引用的是一個值,不是具體的某個變量.

并發(fā)編程并不總是最優(yōu)的,避免并發(fā)陷阱

這兩個并行的例子都使用了線程等待,模擬了一些耗時操作的情形
實際上假設(shè)去掉等待時間, 并行的速度未必會快于串行,這取決于硬件環(huán)境和任務(wù)本身等多重因素

以上面的為例,假設(shè)去掉了等待,在我的電腦上執(zhí)行結(jié)果為:

    public static void main(String[] args) {

        long a = System.currentTimeMillis();
        System.out.print("\n\nstart...");
        String[] strings = new String[20];
        for (int i = 0; i < strings.length; i++) {
//            try {
//                TimeUnit.MILLISECONDS.sleep(300);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
            strings[i] = i + "";
        }
        System.out.print("end: " + (System.currentTimeMillis() - a));


        long b = System.currentTimeMillis();
        System.out.print("\n\nstart...");
        String[] strings2 = new String[20];
        Arrays.parallelSetAll(strings2, i -> {
//            try {
//                TimeUnit.MILLISECONDS.sleep(300);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
            return i + "";
        });
        System.out.print("end: " + (System.currentTimeMillis() - b));


    }

結(jié)果:

start...end: 0

start...end: 66

可以看到初始化一個20長度的數(shù)組,在串行里幾乎不耗費任何時間,反而并行因為需要上下文切換和任務(wù)調(diào)度,消耗了一定的時間.


<b>并行雖好用,也要根據(jù)情況正確的使用.</b>

總結(jié)

本次主題主要介紹什么是函數(shù)式編程,其主要特點, 以及它在 Java8 中是怎么體現(xiàn)的.

計算機發(fā)展至今不到百年,如今更掀起一次又一次的技術(shù)革命,而作為與機器打交道的編程語言更是層出不窮, 作為個體程序員很難一一去學(xué)習(xí)它們, 我們更應(yīng)該去思考編程語言的設(shè)計思想.

引用開頭摘自書中的一段話:

學(xué)習(xí)一種全新的語言,困難不在數(shù)據(jù)類型,控制語句,或數(shù)據(jù)結(jié)構(gòu)----語法不過是些小細節(jié)罷了难衰。
真正應(yīng)該學(xué)習(xí)的是:怎么學(xué)會用另一種方式去思考,去學(xué)習(xí)語言的設(shè)計思想.

函數(shù)式編程是一種編程范式,區(qū)別于命令式注重解決問題的步驟,函數(shù)式注重的數(shù)據(jù)之間的關(guān)系 --- 即 f(x).
它不是某一個語言或者某一種語言特有的,如我們熟悉的 RxJava 就包含了函數(shù)式編程思想,所以 RxJava 也叫函數(shù)式響應(yīng)式編程. 而比較火的 Kotlin, 也可以是面向?qū)ο蟮?也可以是函數(shù)式編程風(fēng)格的.

語言本身只是函數(shù)式思想的具體表達,而我們更應(yīng)該轉(zhuǎn)換的是編寫代碼時的思考方式.

參考資料

什么是函數(shù)式編程思維? - 知乎

Java 8 中的 Streams API 詳解 - IBM

Java 8函數(shù)式編程

函數(shù)式編程思維

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末逗栽,一起剝皮案震驚了整個濱河市盖袭,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌彼宠,老刑警劉巖鳄虱,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異凭峡,居然都是意外死亡拙已,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門摧冀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來倍踪,“玉大人,你說我怎么就攤上這事索昂〗ǔ担” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵椒惨,是天一觀的道長缤至。 經(jīng)常有香客問我,道長康谆,這世上最難降的妖魔是什么领斥? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮沃暗,結(jié)果婚禮上月洛,老公的妹妹穿的比我還像新娘。我一直安慰自己描睦,他們只是感情好膊存,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布导而。 她就那樣靜靜地躺著忱叭,像睡著了一般隔崎。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上韵丑,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天爵卒,我揣著相機與錄音,去河邊找鬼撵彻。 笑死钓株,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的陌僵。 我是一名探鬼主播轴合,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼碗短!你這毒婦竟也來了受葛?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤偎谁,失蹤者是張志新(化名)和其女友劉穎总滩,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體巡雨,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡闰渔,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了铐望。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片冈涧。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖正蛙,靈堂內(nèi)的尸體忽然破棺而出炕舵,到底是詐尸還是另有隱情,我是刑警寧澤跟畅,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布咽筋,位于F島的核電站,受9級特大地震影響徊件,放射性物質(zhì)發(fā)生泄漏奸攻。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一虱痕、第九天 我趴在偏房一處隱蔽的房頂上張望睹耐。 院中可真熱鬧,春花似錦部翘、人聲如沸硝训。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽窖梁。三九已至赘风,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間纵刘,已是汗流浹背邀窃。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留假哎,地道東北人瞬捕。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像舵抹,于是被迫代替她去往敵國和親肪虎。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容