[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

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ǔ)法糖,是外部迭代。


3.2 實(shí)現(xiàn)機(jī)制
一串惰性求值方法+最后的一個(gè)及早求值方法,類(lèi)似于建造者模式。區(qū)分方式:
- 惰性求值方法:返回值是stream
- 及早求值方法:返回值是另一個(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ò)特除處理的方法。


例子:
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 方法引用



除了上面說(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 并行化操作
- Steam對(duì)象用parrallel方法
- 集合調(diào)用parrallelStream方法
并發(fā)并行化一定快。一個(gè)重要因素就是數(shù)據(jù)量。
6.4 模擬系統(tǒng)
模特卡洛模擬法模擬擲骰子
6.5 限制
- reduce方法初始值必須是組合函數(shù)的恒等值。比如求和的初始值是0
- 組合操作必須符合結(jié)合律。就是說(shuō)組合操作的順序不重要。
- 避免持有鎖,流框架會(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ù)組。

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)倒置原則
- 高層模塊不應(yīng)該依賴(lài)于低層模塊,二者都應(yīng)該依賴(lài)于抽象
- 抽象不應(yīng)該依賴(lài)于細(xì)節(jié),細(xì)節(jié)應(yīng)該依賴(lài)于抽象
這幾條原則是非?;A(chǔ)而且重要的面向?qū)ο笤O(shè)計(jì)原則。正是由于這些原則的基礎(chǔ)性,理解、融匯貫通這些原則需要不少的經(jīng)驗(yàn)和知識(shí)的積累。上述的圖片很好的注釋了這幾條原則。