Java 8函數(shù)式編程-讀書(shū)筆記

[TOC]

簡(jiǎn)介

為何修改Java

java8的改變是為了讓代碼在多核CPU上高效運(yùn)行。

面向?qū)ο缶幊?vs. 函數(shù)式編程

面向?qū)ο笫菍?duì)數(shù)據(jù)進(jìn)行抽象;

函數(shù)式編程是對(duì)行為進(jìn)行抽象

函數(shù)式編程的核心:在思考問(wèn)題時(shí),使用不可變值和函數(shù),函數(shù)對(duì)一個(gè)值進(jìn)行處理,映射成另一個(gè)值

lambda表達(dá)式

第一個(gè)lambda表達(dá)式

匿名內(nèi)部類(lèi)是代碼即數(shù)據(jù)的例子。我們傳遞的參數(shù)是代表某種行為的對(duì)象。但是這種方式冗長(zhǎng),需要樣板代碼,而且可讀性很差,沒(méi)有表達(dá)出來(lái)程序員的意圖。我們不想傳入對(duì)象,只想傳入行為。lambda就是傳入一段代碼塊——一個(gè)沒(méi)有名字的函數(shù)。

傳入行為的時(shí)候,參數(shù)不用指定類(lèi)型,編譯器根據(jù)參數(shù)類(lèi)型進(jìn)行推斷,比如是A類(lèi)型的對(duì)象,那么會(huì)找A里面的默認(rèn)方法,這個(gè)方法的參數(shù)類(lèi)型就是lambda表達(dá)式的參數(shù)類(lèi)型。也就是說(shuō),只有一個(gè)方法的接口的匿名內(nèi)部類(lèi)對(duì)象==一個(gè)lambda表達(dá)式

如何辨別lambda表達(dá)式

lambda的幾種形式 :

Runnable noArguments = () -> System.out.println("Hello Wordl");
ActionListener oneArguments = event -> System.out.println("button clicked");
Runnable multiStatement = () -> {
    System.out.println("Hello");
    System.out.println("World");
};
BinaryOperator<Long> add = (x, y) -> x + y;
BinaryOperator<Long> addExplict = (Long x, Long y) -> x + y;

lambda表達(dá)式的目標(biāo)類(lèi)型是函數(shù)接口,但具體是哪個(gè)函數(shù)接口,需要通過(guò)上下文推斷。即根據(jù)lambda被賦值的對(duì)象的類(lèi)型去推斷l(xiāng)ambda表達(dá)式的參數(shù)類(lèi)型。

引用值,而不是變量

匿名內(nèi)部類(lèi)引用外部變量的時(shí)候,外部變量需要是final。lambda是一樣的,但是可以不寫(xiě)final。但是如果改變了變量值也會(huì)報(bào)錯(cuò)。所以lambda也被稱(chēng)為閉包。因?yàn)樾枰俏促x值的變量與周邊環(huán)境隔離起來(lái),進(jìn)而被綁定到一個(gè)特定的值。

這里就參考上面的定義,函數(shù)是對(duì)不可變的值進(jìn)行處理,然后映射成為另一個(gè)值。

函數(shù)接口

lambda本身的類(lèi)型是函數(shù)接口:只有一個(gè)抽象方法的接口。這來(lái)源于java編程以前經(jīng)常使用的一個(gè)技巧,就是使用只有一個(gè)方法的接口來(lái)表示某特定方法并反復(fù)使用。這種用法被改進(jìn)為lambda表達(dá)。但是歷史告訴我們,一個(gè)顯而易見(jiàn)的地方的改進(jìn)往往能帶來(lái)更多不能一眼想到的牛逼用法的改進(jìn)。比如后面的流處理那些。

我理解,函數(shù)式編程需要定義一個(gè)函數(shù)的殼子,然后用的時(shí)候有函數(shù)的實(shí)例。如同類(lèi)與實(shí)例的關(guān)系。但是java不允許這種形式的存在。所以折中的處理就是把函數(shù)殼子放到接口中。函數(shù)的實(shí)例放到接口的匿名內(nèi)部類(lèi)的實(shí)例中。現(xiàn)在lambda改變的是匿名內(nèi)部類(lèi)這種丑陋的形式,讓它看起來(lái)像是一個(gè)簡(jiǎn)潔的函數(shù)了。同時(shí)需要注意,接口中只能定義一個(gè)函數(shù),否則編譯器無(wú)法得知你想用哪個(gè)函數(shù)模板。

直接定義函數(shù)模板,目前java暫時(shí)無(wú)法實(shí)現(xiàn)。所以必須把函數(shù)模板放在接口中,成為函數(shù)接口,即java只能定義一個(gè)接口(或者類(lèi)),但是函數(shù)接口中的那個(gè)函數(shù)實(shí)質(zhì)上就是函數(shù)模板。

但是函數(shù)實(shí)例替換匿名類(lèi)對(duì)象這個(gè)是可以做到的。注意,如果變量或者參數(shù)就是函數(shù)接口的匿名類(lèi)對(duì)象,我們可以直接傳入lambda表達(dá)式。但是如果變量和參數(shù)是其他類(lèi)的匿名類(lèi)對(duì)象呢?如果這個(gè)匿名類(lèi)對(duì)象只有一個(gè)行為,那么我們可以用lambda表示這個(gè)行為,然后在類(lèi)內(nèi)部實(shí)現(xiàn)一個(gè)子類(lèi)+工廠方法,工廠方法的入?yún)⑹莑ambda,返回值是內(nèi)部子類(lèi)。工廠方法內(nèi)調(diào)用子類(lèi)的構(gòu)造函數(shù),其入?yún)⒁彩莑ambda。內(nèi)部子類(lèi)有l(wèi)ambda的引用,然后重寫(xiě)父類(lèi)需要的那個(gè)行為(那個(gè)函數(shù)),函數(shù)內(nèi)部使用lambda就行了。這里的lambda一般是supplier。

下面是ThreadLocal的例子:

// ThreadLocal內(nèi)部實(shí)現(xiàn)
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
    return new SuppliedThreadLocal<>(supplier);
}

static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {

    private final Supplier<? extends T> supplier;

    SuppliedThreadLocal(Supplier<? extends T> supplier) {
        this.supplier = Objects.requireNonNull(supplier);
    }

    @Override
    protected T initialValue() {
        return supplier.get();
    }
}

// 傳統(tǒng)方式
private static ThreadLocal<String> name = new ThreadLocal<String>() {
    @Override
    protected String initialValue() {
        return "default";
    }
};

// 使用lambda后
private static ThreadLocal<String> name = ThreadLocal.withInitial(() -> "default");

我們需要的是ThreadLocal的子類(lèi),返回的SuppliedThreadLocal實(shí)例的確是它的子類(lèi)。

我們需要子類(lèi)實(shí)現(xiàn)initialValue方法,SuppliedThreadLocal實(shí)例的確實(shí)現(xiàn)了,只不過(guò)它的行為使用lambda表達(dá)的。

到這里,我們可以看到,我們可以用lambda的函數(shù)模板去調(diào)用可能出現(xiàn)的函數(shù)實(shí)例。和原來(lái)java代碼中使用了抽象方法,運(yùn)行的時(shí)候?qū)嶋H使用的是子類(lèi)方法一樣。實(shí)際運(yùn)行的時(shí)候傳入的是什么lambda(子類(lèi)方法),函數(shù)模板(抽象方法)實(shí)際運(yùn)行的就是什么。

這樣我們可以把函數(shù)模板放在其他函數(shù)的函數(shù)體中,這樣感覺(jué)lambda能發(fā)揮作用的空間就更大了。

  • supplier是0進(jìn)1出,方法是get
  • consumer是1進(jìn)0出,方法是accept
  • function是1進(jìn)1出,方法是apply
  • predicate是1進(jìn)1判斷,方法是test
  • BiConsumer是2進(jìn)0出,方法是accept
  • BiFunction是2進(jìn)1出,方法是apply
  • Bipredictate是2進(jìn)1判斷,方法也是test
重要的函數(shù)接口.png

2.5 類(lèi)型推斷

java7中的菱形操作符就是類(lèi)型推斷的一種。主要是有賦值給變量和賦值給函數(shù)參數(shù)兩種,根據(jù)變量或者參數(shù)的類(lèi)型,都可以推斷。

3 流

對(duì)核心類(lèi)庫(kù)的改進(jìn)主要包括集合類(lèi)的API和新引入的流(Stream)。流能讓程序員得以站在更高的抽象層次上對(duì)集合進(jìn)行操作。

stream類(lèi)的每個(gè)方法都對(duì)應(yīng)集合上的一種操作。它是用函數(shù)式編程方式在集合類(lèi)上進(jìn)行復(fù)雜操作的工具。

3.1 從外部迭代到內(nèi)部迭代

對(duì)集合最常見(jiàn)的操作就是迭代,但是樣板代碼多。另外,如果集合元素很多,想并行提高效率也很麻煩。最后,代碼表意性不強(qiáng)。

for循環(huán)是封裝iterator的語(yǔ)法糖,是外部迭代。

圖片.png
圖片.png

3.2 實(shí)現(xiàn)機(jī)制

一串惰性求值方法+最后的一個(gè)及早求值方法,類(lèi)似于建造者模式。區(qū)分方式:

  1. 惰性求值方法:返回值是stream
  2. 及早求值方法:返回值是另一個(gè)值或者為空

這樣在對(duì)需要什么樣的操作和結(jié)果有更多的了解,可以更有效率地進(jìn)行計(jì)算。

3.3 常用操作

了解一下comparator中的comparing方法。

Track shortestTrack = tracks.stream()
    // 方式1,最傳統(tǒng)的匿名對(duì)象
    .min(new Comparator<Track>(){
        @Override
        public int compare(Track o1, Track o2) {
            if(o1.getLength()>o2.getLength())
                return 1;
            else
                return 0;
        }
    })

    // 方式2,用lambda初步改進(jìn)
    .min((Comparator<Track>) (o1, o2) -> {
        if(o1.getLength()>o2.getLength())
            return 1;
        else
            return 0;
    })
    
    // 方式3,進(jìn)一步改進(jìn)
    .min(Comparator.comparing(track -> track.getLength()))
    
    .get();

// 實(shí)現(xiàn)
public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
    Function<? super T, ? extends U> keyExtractor)
{
    Objects.requireNonNull(keyExtractor);
    return (Comparator<T> & Serializable)
        (c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
}

方式3本質(zhì)思路就是把方式2的模板代碼也省掉,我來(lái)替你寫(xiě)兩個(gè)對(duì)象比較,加if和else的部分,因?yàn)檫@部分操作也是不變的,你只要給我比較對(duì)象就行了。

然后給的方式不是直接給,因?yàn)楹芏鄷r(shí)候都是要有取對(duì)象的行為的。所以通過(guò)Function函數(shù)接口給。那么lambda的返回值就是比較對(duì)象了。這個(gè)對(duì)象需要是可比較的,就是實(shí)現(xiàn)中的U,需要已經(jīng)實(shí)現(xiàn)了Comparable接口?;A(chǔ)數(shù)據(jù)類(lèi)型都是實(shí)現(xiàn)了的。

然后在實(shí)現(xiàn)內(nèi)部,jdk幫我們寫(xiě)了方式2的表達(dá)式,因?yàn)闆](méi)有賦值給本地變量,直接return的,所以要強(qiáng)制類(lèi)型轉(zhuǎn)換一下。這里有個(gè)點(diǎn),強(qiáng)制類(lèi)型轉(zhuǎn)換也可以是一個(gè)類(lèi)加多個(gè)接口的。

總結(jié):comparing方法接收一個(gè)函數(shù),返回另一個(gè)函數(shù)。

重構(gòu)建議

所有的返回List和Set的函數(shù)都可以換成返回Stream,它很好的封裝了內(nèi)部實(shí)現(xiàn)的數(shù)據(jù)接口,僅僅暴露一個(gè)Stream接口,用戶(hù)實(shí)際使用不會(huì)影響內(nèi)部的List或者set。

3.4 重構(gòu)遺留代碼

每次修改都要單測(cè)。

如果函數(shù)的參數(shù)或者返回值,有任何一個(gè)是函數(shù),那么這個(gè)函數(shù)就被稱(chēng)為高階函數(shù)。

3.7 正確使用lambda表達(dá)式

明確要達(dá)成什么轉(zhuǎn)化,而不是說(shuō)明如何轉(zhuǎn)化,這樣代碼簡(jiǎn)介,缺陷少,表意清晰。而且沒(méi)有副作用。只通過(guò)函數(shù)返回值就能充分理解函數(shù)的全部作用。

沒(méi)有副作用的函數(shù)不會(huì)改變程序或者外界的狀態(tài)。打印就算是副作用。

鼓勵(lì)用戶(hù)使用lambda表達(dá)式獲取值而不是變量。這樣用戶(hù)就更容易寫(xiě)出沒(méi)有副作用的代碼。

4 類(lèi)庫(kù)

java8引入默認(rèn)方法和接口的靜態(tài)方法。

4.2 基本類(lèi)型

java中沒(méi)有List<int>,只有List<Integer>,因?yàn)榉盒褪菍?duì)包裝類(lèi)型的。包裝類(lèi)型相對(duì)更占空間,而且裝箱拆箱的時(shí)間也是很多的。

stream對(duì)用得最多的3中基本類(lèi)型做了區(qū)分。有對(duì)應(yīng)的stream。如果可能,盡量用對(duì)基本類(lèi)型做過(guò)特除處理的方法。

圖片.png

圖片.png

例子:

IntSummaryStatistics trackLengthStats
            = album.getTracks()
                .mapToInt(track -> track.getLength())
                .summaryStatistics();

System.out.printf("Max: %d, Min: %d, Ave: %f, Sum: %d",
                  trackLengthStats.getMax(),
                  trackLengthStats.getMin(),
                  trackLengthStats.getAverage(),
                  trackLengthStats.getSum());

4.3 重載

選擇更具體的,比如下面選擇參數(shù)是IntegerBiFunction的,因?yàn)橥ㄟ^(guò)繼承,IntegerBiFunction更具體。如果沒(méi)有繼承就無(wú)法區(qū)分,就編譯報(bào)錯(cuò)。

overloadedMethod((x, y) -> x + y);

// BEGIN most_specific_bifunction
private interface IntegerBiFunction extends BinaryOperator<Integer> {

}

private void overloadedMethod(BinaryOperator<Integer> lambda) {
    System.out.print("BinaryOperator");
}

private void overloadedMethod(IntegerBiFunction lambda) {
    System.out.print("IntegerBinaryOperator");
}

4.4 @FunctionalInterface

用作函數(shù)接口的接口都應(yīng)該添加,如果不是就不加,比如Closeable和Comparable接口,雖然是帶有一個(gè)方法的接口。加了這個(gè)注釋?zhuān)琷avac會(huì)檢查是否符合。

4.5678 二進(jìn)制接口的兼容性 & 默認(rèn)方法

為了向前兼容,自己項(xiàng)目中實(shí)現(xiàn)了Collection接口的類(lèi)也需要增加Stream方法。否則編譯不通過(guò)。避免的方式就是使用默認(rèn)方法。

Collection接口中有默認(rèn)方法,那就允許子類(lèi)不實(shí)現(xiàn)它。比如Iterable接口新增默認(rèn)方法:forEach。

默認(rèn)方法的繼承,用到時(shí)候再看看

4.9 接口的靜態(tài)方法

就是用作工具方法,和這個(gè)接口緊密相關(guān)的。比如stream.of方法。

4.10 Optional

之前看過(guò),沒(méi)啥用

5 高級(jí)集合類(lèi)和收集器

5.1 方法引用

image.png
image.png
image.png

除了上面說(shuō)的,還有構(gòu)造函數(shù)

(name, nationality) -> new Artist(name, nationality)
Artist::new 
    
String[]::new 

5.2 元素順序

看集合本身是否有序。大多數(shù)流操作都是在有序集合上效率高。如果希望有序,用forEachOrdered方法。少數(shù)操作在無(wú)序上快,用unordered方法消除順序。

5.3 使用收集器

如下的功能都是用Collectors里面的方法實(shí)現(xiàn)的,返回的都是不同功能的收集器(collector)

5.3.1 轉(zhuǎn)換成其他集合

具體的集合實(shí)現(xiàn)類(lèi)不需要你指定。如果希望指定,用toCollection

stream.collection(toCollection(TreeSet::new))

5.3.2 轉(zhuǎn)換成值

maxBy和minBy。averagingInt

public Optional<Artist> biggestGroup(Stream<Artist> artists) {
    Function<Artist,Long> getCount = artist -> artist.getMembers().count();
    return artists.collect(maxBy(comparing(getCount)));// comparing是靜態(tài)導(dǎo)入的
}

5.3.3 數(shù)據(jù)分塊

用true和false分解成兩個(gè)集合。partitioningBy。

public Map<Boolean, List<Artist>> bandsAndSoloRef(Stream<Artist> artists) {
    return artists.collect(partitioningBy(Artist::isSolo));
}

5.3.4 數(shù)據(jù)分組

用任意數(shù)值對(duì)數(shù)據(jù)分組。groupingBy。類(lèi)似數(shù)據(jù)庫(kù)中的group by操作。

    // 專(zhuān)輯按照藝術(shù)家分組
public Map<Artist, List<Album>> albumsByArtist(Stream<Album> albums) {
    return albums.collect(groupingBy(album -> album.getMainMusician()));
}

5.3.5 字符串

最后輸出一個(gè)特定格式的字符串。joining。格式化藝術(shù)家姓名。

String result =
    artists.stream()
              .map(Artist::getName)
              .collect(Collectors.joining(", ", "[", "]"));

5.3.6 組合收集器

有些需求用一個(gè)收集器不能滿(mǎn)足,可以用多個(gè),但是要有技巧。介紹兩種方式。

一是

    // 每個(gè)藝術(shù)家的專(zhuān)輯數(shù),參考5.3.4
public Map<Artist, Long> numberOfAlbums(Stream<Album> albums) {
    return albums.collect(groupingBy(album -> album.getMainMusician(),
                                     counting()));

二是,這里用mapping告訴groupingBy將它的值作為映射,生成最終結(jié)果。

// 每個(gè)藝術(shù)家的專(zhuān)輯名稱(chēng)
public Map<Artist, List<String>> nameOfAlbums(Stream<Album> albums) {
    return albums.collect(groupingBy(Album::getMainMusician,
                                     mapping(Album::getName, toList())));

兩個(gè)例子都用到了第二個(gè)收集器,用于手機(jī)最終結(jié)果的一個(gè)子集。它們是下游收集器。

5.3.7 重構(gòu)和定制收集器

如果項(xiàng)目需要,可以重構(gòu)和定制收集器。

細(xì)節(jié)用到再看看

5.3.8 對(duì)收集器的歸一化處理

沒(méi)看懂

5.4 一些細(xì)節(jié)

首先,構(gòu)建map時(shí),嘗試取值,如果沒(méi)有,創(chuàng)建新值并返回。

java8中map新方法,computeIfAbsent

// 舊的方法
public Artist getArtist(String name) {
    Artist artist = artistCache.get(name);
    if (artist == null) {
        artist = readArtistFromDB(name);
        artistCache.put(name, artist);
    }
    return artist;
}
    
// 新方法
public Artist getArtist(String name) {
    return artistCache.computeIfAbsent(name, this::readArtistFromDB);
}

還有putIfAbsent,compute。

第二,迭代map

// 舊方法
Map<Artist, Integer> countOfAlbums = new HashMap<>();
for (Map.Entry<Artist, List<Album>> entry : albumsByArtist.entrySet()) {
    Artist artist = entry.getKey();
    List<Album> albums = entry.getValue();
    countOfAlbums.put(artist, albums.size());
}

// 新方法
Map<Artist, Integer> countOfAlbums = new HashMap<>();
albumsByArtist.forEach((artist, albums) -> {
    countOfAlbums.put(artist, albums.size());
});
            

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

并發(fā)(concurrency)和并行(parallellism)的區(qū)分。本章討論數(shù)據(jù)并行化,一種特殊形式的并行化。把數(shù)據(jù)分成塊,為每塊數(shù)據(jù)分配單獨(dú)的處理單元。

另一種是任務(wù)并行化,它是線(xiàn)程不同,工作各異。比如servlet服務(wù)器。

并行化可以利用現(xiàn)代CPU架構(gòu),因?yàn)榉较蚓褪嵌嗪?,而不是高主頻。

6.3 并行化操作

  1. Steam對(duì)象用parrallel方法
  2. 集合調(diào)用parrallelStream方法

并發(fā)并行化一定快。一個(gè)重要因素就是數(shù)據(jù)量。

6.4 模擬系統(tǒng)

模特卡洛模擬法模擬擲骰子

6.5 限制

  1. reduce方法初始值必須是組合函數(shù)的恒等值。比如求和的初始值是0
  2. 組合操作必須符合結(jié)合律。就是說(shuō)組合操作的順序不重要。
  3. 避免持有鎖,流框架會(huì)自己處理同步,程序員不需要自己加。

6.6 性能

影響因素

  • 數(shù)據(jù)大小

數(shù)據(jù)夠大才有意義。

  • 源數(shù)據(jù)的結(jié)構(gòu)
  • 裝箱

基本類(lèi)型比裝箱類(lèi)型快

  • 核心數(shù)量
  • 單元處理開(kāi)銷(xiāo)

花在流中每個(gè)元素身上的時(shí)間越長(zhǎng),并行操作帶來(lái)的性能提升越明顯。

在底層,并行流還是用的fork/join框架。fork遞歸式地分解問(wèn)題,然后每段并行執(zhí)行,最終由join合并結(jié)果,返回最后的值。

能重復(fù)將數(shù)據(jù)結(jié)構(gòu)對(duì)半分解的難易程度,決定了分解操作的快慢。能對(duì)半分解同時(shí)意味著待分解的值能夠被等量地分解。

根據(jù)性能,核心類(lèi)庫(kù)的數(shù)據(jù)結(jié)構(gòu)分3組:

  • 性能好

ArrayList、數(shù)組或者IntStream.range,支持隨機(jī)讀取,能輕易被分解。

  • 性能一般

HashSet、TreeSet,不易被公平分解,但大多數(shù)時(shí)候分解是可能的。

  • 性能差

LinkedList,Stream.iterate、BufferedReader.lines,難于分解,可能需要O(N)

求和,LinkedList可能比ArrayList慢10倍。

分解后的操作可能是有狀態(tài)的無(wú)狀態(tài)的。無(wú)狀態(tài)操作包括map、filter、flatMap,有狀態(tài)的包括sorted、distinct和limit。盡量避開(kāi)有狀態(tài)的。

6.7 并行化數(shù)組操作

針對(duì)數(shù)組的,脫離流框架的操作。都在Arrays中。以下是新增的并行化操作。都是修改原來(lái)的數(shù)組,不會(huì)產(chǎn)生新數(shù)組。

圖片.png

parallelSetAll初始化數(shù)組

// BEGIN simpleMovingAverage
public static double[] simpleMovingAverage(double[] values, int n) {
    double[] sums = Arrays.copyOf(values, values.length); // <1>
    Arrays.parallelPrefix(sums, Double::sum); // <2>
    int start = n - 1;
    return IntStream.range(start, sums.length) // <3>
        .mapToDouble(i -> {
            double prefix = i == start ? 0 : sums[i - n];
            return (sums[i] - prefix) / n; // <4>
        })
        .toArray(); // <5>
}
// END simpleMovingAverage

// BEGIN parallelInitialize
public static double[] parallelInitialize(int size) {
    double[] values = new double[size];
    Arrays.parallelSetAll(values, i -> i);
    return values;
}
// END parallelInitialize

parallelPrefix擅長(zhǎng)對(duì)時(shí)間序列數(shù)據(jù)做累加,會(huì)更新一個(gè)數(shù)組,將每個(gè)元素替換為當(dāng)前元素和其前驅(qū)元素的和(和是一個(gè)BinaryOperator,未必一定是加和)。

7 測(cè)試、調(diào)試和重構(gòu)

7.1 重構(gòu)的候選項(xiàng)

  • 進(jìn)進(jìn)出出、搖搖晃晃

日志isDebugEnabled的例子。如果代碼中不斷檢查和操作某個(gè)對(duì)象,只是為了最后給它設(shè)定一個(gè)值,那么這段代碼就本該屬于你所操作的對(duì)象。

  • 孤獨(dú)的覆蓋

使用繼承,目的只是為了覆蓋一個(gè)方法。例子就是ThreadLocal。

  • 同樣的東西寫(xiě)兩遍
// 改進(jìn)前
public long countRunningTime() {
    long count = 0;
    for (Album album : albums) {
        for (Track track : album.getTrackList()) {
            count += track.getLength();
        }
    }
    return count;
}

public long countMusicians() {
    long count = 0;
    for (Album album : albums) {
        count += album.getMusicianList().size();
    }
    return count;
}

public long countTracks() {
    long count = 0;
    for (Album album : albums) {
        count += album.getTrackList().size();
    }
    return count;
}
// END body

// 改進(jìn)后
public long countFeature(ToLongFunction<Album> function) {
    return albums.stream()
        .mapToLong(function)
        .sum();
}

public long countTracks() {
    return countFeature(album -> album.getTracks().count());
}

public long countRunningTime() {
    return countFeature(album -> album.getTracks()
                        .mapToLong(track -> track.getLength())
                        .sum());
}

public long countMusicians() {
    return countFeature(album -> album.getMusicians().count());
}

7.2 lambda表達(dá)式的單測(cè)

lambda沒(méi)有名字,無(wú)法直接在測(cè)試代碼中調(diào)用。

任何一個(gè)lambda表達(dá)式都能被改寫(xiě)為普通方法,然后使用方法引用直接引用。

7.3 測(cè)試替身

使用Mockito框架

7.4567 流的調(diào)試

循環(huán)由框架處理,斷點(diǎn)沒(méi)法打,不好調(diào)試。用peak。它能查看每個(gè)值,同時(shí)能繼續(xù)操作流。還能在其中打斷點(diǎn)。

Set<String> nationalities
                = album.getMusicians()
                .filter(artist -> artist.getName().startsWith("The"))
                .map(artist -> artist.getNationality())
                .peek(nation -> System.out.println("Found nationality: " + nation))
                .collect(Collectors.<String>toSet());

8 設(shè)計(jì)和架構(gòu)的原則

8.1 lambda與設(shè)計(jì)模式

1和2的主要的思想就是如果設(shè)計(jì)模式中有接口符合函數(shù)接口的形式,我們就都可以用lambda表達(dá)式替換,甚至直接用方法引用。

8.1.1 命令者模式

8.1.2 策略模式

8.1.3 觀察者模式

8.1.4 模板方法

8. lambda與DSL

用不到先不看

8. lambda與SOLID原則

S.O.L.I.D是面向?qū)ο笤O(shè)計(jì)和編程(OOD&OOP)中幾個(gè)重要編碼原則(Programming Priciple)的首字母縮寫(xiě)。

SRP [The Single Responsibility Principle ] 單一責(zé)任原則
OCP [The Open Closed Principle] 開(kāi)放封閉原則
LSP [The Liskov Substitution Principle] 里氏替換原則
ISP [The Interface Segregation Principle] 接口分離原則
DIP [The Dependency Inversion Principle] 依賴(lài)倒置原則

單一責(zé)任原則:
當(dāng)需要修改某個(gè)類(lèi)的時(shí)候原因有且只有一個(gè)(THERE SHOULD NEVER BE MORE THAN ONE REASON FOR A CLASS TO CHANGE)。換句話(huà)說(shuō)就是讓一個(gè)類(lèi)只做一種類(lèi)型責(zé)任,當(dāng)這個(gè)類(lèi)需要承當(dāng)其他類(lèi)型的責(zé)任的時(shí)候,就需要分解這個(gè)類(lèi)。

開(kāi)放封閉原則
軟件實(shí)體應(yīng)該是可擴(kuò)展,而不可修改的。也就是說(shuō),對(duì)擴(kuò)展是開(kāi)放的,而對(duì)修改是封閉的。這個(gè)原則是諸多面向?qū)ο缶幊淘瓌t中最抽象、最難理解的一個(gè)。

里氏替換原則
當(dāng)一個(gè)子類(lèi)的實(shí)例應(yīng)該能夠替換任何其超類(lèi)的實(shí)例時(shí),它們之間才具有is-A關(guān)系

接口分離原則
不能強(qiáng)迫用戶(hù)去依賴(lài)那些他們不使用的接口。換句話(huà)說(shuō),使用多個(gè)專(zhuān)門(mén)的接口比使用單一的總接口總要好。

依賴(lài)倒置原則

  1. 高層模塊不應(yīng)該依賴(lài)于低層模塊,二者都應(yīng)該依賴(lài)于抽象
  2. 抽象不應(yīng)該依賴(lài)于細(xì)節(jié),細(xì)節(jié)應(yīng)該依賴(lài)于抽象

這幾條原則是非?;A(chǔ)而且重要的面向?qū)ο笤O(shè)計(jì)原則。正是由于這些原則的基礎(chǔ)性,理解、融匯貫通這些原則需要不少的經(jīng)驗(yàn)和知識(shí)的積累。上述的圖片很好的注釋了這幾條原則。

9 lambda寫(xiě)并發(fā)程序

9.2 回調(diào)

9.3 消息傳遞架構(gòu)

9.4 末日金字塔

9.5 Future

9.6 CompletableFuture

9.7 響應(yīng)式編程

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 一、meta標(biāo)簽的三個(gè)屬性 title: 30字以?xún)?nèi),簡(jiǎn)約且全面,避免重復(fù),可以用主關(guān)鍵詞+吸引用戶(hù)詞+品牌詞,以...
    TTTXTTT閱讀 376評(píng)論 0 1
  • 題記:第一次作為組織者參加社群活動(dòng),有點(diǎn)小激動(dòng),結(jié)束了還有點(diǎn)小小的成就感,從接到值月生的安排到彩排到正式舉行這次頒...
    等在_深秋閱讀 409評(píng)論 1 1
  • 本文將會(huì)完整的講述ES中搜索建議的程序?qū)崿F(xiàn)。在網(wǎng)上搜索的教程當(dāng)中大部分都是使用的ES的TransportClien...
    社交達(dá)人叔本華閱讀 3,266評(píng)論 0 2
  • 現(xiàn)如今的公司再也不像父輩們那樣一份工作干到退休了,隨著公司頻繁換人,員工頻繁換工作,就導(dǎo)致了一個(gè)問(wèn)題,公司不信任員...
    纖陌顏閱讀 243評(píng)論 2 3
  • 近況去了招聘會(huì) 各種瞎聊 發(fā)現(xiàn)自己瞎聊的技巧還好。。。就是拿不到refer也拿不到offer。。。 也投了十幾個(gè)公...
    S喵閱讀 281評(píng)論 0 0

友情鏈接更多精彩內(nèi)容