Java8 in action
- 沒有共享的可變數(shù)據(jù),將方法和函數(shù)即代碼傳遞給其他方法的能力就是我們平常所說的函數(shù)式編程范式的基石。
- Collection主要是為了存儲(chǔ)和訪問數(shù)據(jù),而Stream則主要用于描述對(duì)數(shù)據(jù)的計(jì)算。這里的關(guān)鍵點(diǎn)在于,Stream允許并提倡并行處理一個(gè)Stream中的元素。
通過行為參數(shù)化傳遞代碼
- 行為參數(shù)化就是可以幫助你處理頻繁變更的需求的一種軟件開發(fā)模式。它意味著拿出一個(gè)代碼塊,把它準(zhǔn)備好卻不去執(zhí)行它。這個(gè)代碼塊以后作為參數(shù)傳遞給另一個(gè)方法,稍后再去執(zhí)行它。
- 行為參數(shù)化,就是一個(gè)方法接受多個(gè)不同的地為作為參數(shù),并在內(nèi)部使用它們,完成不同行為的能力。行為參數(shù)化可以讓代碼更好地適應(yīng)不斷變化的要求,減輕未來的工作量。
- 傳遞代碼,就是將新行為作為參數(shù)傳遞給方法。但在Java8之前這實(shí)現(xiàn)起來很啰嗦。為接口聲明許多只用一次的實(shí)體類而造成的啰嗦代碼,在Java8之前可以用匿名類來減少。
Lambda表達(dá)式
- 可以把Lambda表達(dá)式理解為簡(jiǎn)潔地表示可傳遞的匿名函數(shù)的一種方式:
- 匿名-----它不像普通方法那樣有一個(gè)明確的名稱,寫的少而想的多
- 函數(shù)-----我們說它是函數(shù),因?yàn)長(zhǎng)ambda函數(shù)不像方法那樣屬于某個(gè)特定的類。但和方法一樣,Lambda有參數(shù)列表,函數(shù)主體,返回類型,還可能有可以拋出的異常列表。
- 傳遞------Lambda表達(dá)式可以作為參數(shù)傳遞給方法或存儲(chǔ)在變量中。
- 簡(jiǎn)潔------無需像匿名類那樣寫很多模板代碼。
- 在哪里使用Lambda表達(dá)式,在函數(shù)式接口上使用Lambda表達(dá)式
- 函數(shù)式接口:就是只定義一個(gè)抽象方法的接口。Lambda表達(dá)式允許你直接以內(nèi)聯(lián)的形式為函數(shù)式接口的抽象方法提供實(shí)現(xiàn),并把整個(gè)表達(dá)式作為函數(shù)式接口的實(shí)例。你用匿名內(nèi)部類也可以完成同樣的事情,只不過比較笨拙:需要提供一個(gè)實(shí)現(xiàn),然后再直接內(nèi)聯(lián)將它實(shí)例化。
- 函數(shù)式接口的抽象方法的簽名基本上就是Lambda表達(dá)式的簽名。我們將這種抽象方法叫作函數(shù)描述符。
- 把Lambda付諸實(shí)踐:環(huán)繞執(zhí)行模式
- 第一步:記得行為參數(shù)化,傳遞行為正是Lambda的拿手好戲
- 第二步,使用函數(shù)式接口來傳遞行為
- 第三步,執(zhí)行一個(gè)行為 Lambda表達(dá)式允許你直接內(nèi)聯(lián),為函數(shù)式接口的抽象方法提供實(shí)現(xiàn),并且將整個(gè)表達(dá)式作為函數(shù)式接口的一個(gè)實(shí)例。
- 第四步:傳遞Lambda
- 使用函數(shù)式接口。 Java8的設(shè)計(jì)師在java.util.function包中引入了幾個(gè)新的函數(shù)式接口。
- Predicate java.util.function.Predict<T> 接口定義了一個(gè)名為test的抽象方法,它接受泛型T對(duì)象,并返回一個(gè)boolean。在你需要表示一個(gè)涉及類型T的布爾表達(dá)式時(shí),就可以使用這個(gè)接口。
- Consumer java.util.function.Consumer<T> 定義了一個(gè)名叫accept的抽象方法,它接受泛型T的對(duì)象,沒有返回(void).你如果需要訪問類型T對(duì)象,并對(duì)其執(zhí)行某些操作,就可以使用這個(gè)接口。
- Function java.util.function.Function<T,R>接口定義了一個(gè)叫作apply的方法,它接受一個(gè)泛型T的對(duì)象,并返回一個(gè)泛型R的對(duì)象。如果你需要定義一個(gè)Lambda,將輸入對(duì)象的信息映射到輸出,就可以使用這個(gè)接口。
- 原始類型特化 上面的三個(gè)泛型函數(shù)式接口:Predicate<T>, Consumer<T>和Function<T,R>。還有些函數(shù)式接口專為某些類型而設(shè)計(jì)。 Java類型要么是引用類型(如,Byte,Integer,Object,List),要么是原始類型(比如:int,double,byte,char).但是泛型只能綁定到引用類型。 Java8為我們前面所說的函數(shù)式接口帶來了一個(gè)專門的版本,以便在輸入和輸出都是原始類型時(shí)避免自動(dòng)裝箱的操作。 一般來說,針對(duì)專門的輸入?yún)?shù)類型的函數(shù)式接口的名稱都要加上對(duì)應(yīng)的原始類型前綴,如DoublePredicate,IntConsumer,LongBinaryOperator,IntFunction等。Function接口還有針對(duì)輸出參數(shù)類型的變種:ToIntFunction<T>,IntToDoubleFunction等
- 類型檢查,類型推斷以及限制
- 類型檢查 Lambda的類型是從使用Lambda的上下文推斷出來的。上下文(比如,接受它傳遞的方法的參數(shù),或接受它的值的局部變量)中Lambda表達(dá)式需要的類型稱為目標(biāo)類型。
- 同樣的Lambda,不同的函數(shù)式接口。有了目標(biāo)類型的概念,同一個(gè)Lambda表達(dá)式就可以與不同的函數(shù)式接口聯(lián)系起來,只要它們的抽象方法簽名能夠兼容。 -------特殊的void兼容規(guī)則 如果一個(gè)Lambda的主體是一個(gè)語句表達(dá)式,它就和一個(gè)返回void的函數(shù)描述符兼容(當(dāng)然需要參數(shù)列表也兼容)。如,以下兩行都是合法的,盡管List的add方法返回一個(gè)boolean,而不是Consumer上下文(T->void)所要求的void:
//Predicate返回一個(gè)booean
Predicate<String> p = s->list.add(s)
//Consumer返回一個(gè)void
Consumer<String> b = s->list.add(s);
- 類型推斷 Java編譯器會(huì)從上下文(目標(biāo)類型)推斷出用什么函數(shù)式接口來配合Lambda表達(dá)式,這意味著它也可以推斷出適合Lambda的簽名,因?yàn)楹瘮?shù)描述符可以通過目標(biāo)類型來得到。這樣做的好處在于,編譯器可以了解Lambda表達(dá)式的參數(shù)類型,這樣就可以在Lambda語法中省去標(biāo)注參數(shù)類型。
- 使用局部變量。 Lambda表達(dá)式也允許使用自由變量(不是參數(shù),而是在外層作用域中定義的變量),就像匿名類一樣。它們被稱作捕獲Lambda. 關(guān)于能做這些變量做什么有一些限制。Lambda可以沒有限制地捕獲(也就是在其主體中引用)實(shí)例變量和靜態(tài)變量。但局部變量必須聲明為final,或事實(shí)上是final.換句話說,Lambda表達(dá)式只能捕獲指派給它們的局部變量一次。(注:捕獲實(shí)例變量可以被看作捕獲最終局部變量this)
- 方法引用 方法引用讓你可以重復(fù)使用現(xiàn)有的方法定義,并像Lambda一樣傳遞它們。方法引用就是讓你根據(jù)已有的方法實(shí)現(xiàn)來創(chuàng)建Lambda表達(dá)式??梢园逊椒ㄒ?看作針對(duì)僅僅涉及單一方法的Lambda語法糖,因?yàn)槟惚磉_(dá)同樣的事情時(shí)要寫的代碼更少了。方法引用主要有三類
- 指向靜態(tài)方法的方法引用
- 指向任意類型實(shí)例方法的方法引用(如String的length方法)
- 指向現(xiàn)有對(duì)象的實(shí)例方法的引用
- 構(gòu)造函數(shù)引用 對(duì)于一個(gè)現(xiàn)有的構(gòu)造函數(shù),你可以利用它的名稱和關(guān)鍵字new來創(chuàng)建它的一個(gè)引用:ClassName:new。 它的功能與指向靜態(tài)方法的引用類似。
- 在需要函數(shù)式接口的地方可以使用Lambda表達(dá)式。函數(shù)式接口就是僅僅定義一個(gè)抽象方法的接口。抽象方法的簽名(稱為函數(shù)描述符)描述了Lambda表達(dá)式的簽名。
- 復(fù)合Lamda表達(dá)式的有用方法。 Java8的好幾個(gè)函數(shù)式接口都有為方便而設(shè)計(jì)的方法。具體而言,許多函數(shù)式接口,比如用于傳遞Lambda表達(dá)式的Comparator,Function和Predicate都提供了允許你進(jìn)行復(fù)合的方法。 在實(shí)踐中,這意味著你可以把簡(jiǎn)單的Lambda復(fù)合成復(fù)雜的表達(dá)式。如,你可以讓兩個(gè)謂詞之間做一個(gè)or操作,組合成一個(gè)更大的謂詞。而且,你可以讓一個(gè)函數(shù)的結(jié)果成為另一個(gè)函數(shù)的輸入。你可能會(huì)想,函數(shù)式接口中怎么可能有更多的方法呢?竅門在于,我們即將介紹的方法都是默認(rèn)方法,也就是說它們不是抽象方法。
- 謂詞復(fù)合 謂詞接口包括三個(gè)方法:negate,and和or,讓重用已有的Predicate來創(chuàng)建更復(fù)雜的謂詞。如,你可使用negate方法返回一個(gè)Predicate的非。
- 函數(shù)復(fù)合 你還可以把Function接口所代表的Lambda表達(dá)式復(fù)合起來。Funcation接口為此配了andThen和compose兩個(gè)默認(rèn)方法,它們都返回Function的一個(gè)實(shí)例。andThen方法會(huì)返回一個(gè)函數(shù),它先對(duì)輸入應(yīng)用一個(gè)給定函數(shù),再對(duì)輸出應(yīng)用另一個(gè)函數(shù)。你也可以類似地使用compose方法,先把給定的函數(shù)用作compose的參數(shù)里面給的那個(gè)函數(shù),然后再把函數(shù)本身用于結(jié)果。
- 判斷一個(gè)操作是惰性求值還是及早求值很簡(jiǎn)單:只需看它的返回值。如果返回值是Stream,那么是惰性求值,如果返回值是另一個(gè)值或?yàn)榭?,那么就是及早求值。使用這些操作的理想方式就是形成一個(gè)惰性求值的鏈,最后用一個(gè)及早求值的操作返回想要的結(jié)果。
小結(jié):
- Lambda表達(dá)式可以理解為一種匿名函數(shù),它沒有名稱,但有參數(shù)列表,函數(shù)主體,返回類型,可能還有一個(gè)可以拋出的異常列表
- 只有在接受函數(shù)式接口的地方才可以使用Lambda表達(dá)式
- Lambda表達(dá)式允許你直接內(nèi)聯(lián),為函數(shù)式接口的抽象方法提供實(shí)現(xiàn),并且將整個(gè)表達(dá)式作為函數(shù)式接口的一個(gè)實(shí)例。
- 為了避免裝箱操作,對(duì)Predicate<T>和Function<T,R>等通用函數(shù)式接口的原始類型特化:IntPredicate,IntToLongFunction等。
- 環(huán)繞執(zhí)行模式(即在方法所必須的代碼中間,你需要執(zhí)行點(diǎn)什么操作,如資源分配 和清理)可以配合Lambda提高靈活性和可重用性。
- Lambda表達(dá)式所需要代表的類型稱為目標(biāo)類型
函數(shù)式數(shù)據(jù)處理
引入流
- 流是什么 流是Java API的新成員,它允許你以聲明方式處理數(shù)據(jù)集合
- Java8中的Stream API可以讓你寫出這樣的代碼:聲明性--更簡(jiǎn)潔,更易讀; 可復(fù)合--更靈活; 可并行--性能更好
- 流簡(jiǎn)介 流到底是什么呢?簡(jiǎn)短的定義就是“從支持?jǐn)?shù)據(jù)處理操作的源生成的元素序列”。讓我們一步步剖析這個(gè)定義。
- 元素序列 像集合一樣,流也提供了一個(gè)接口,可以訪問特定元素類型的一組有序值。因?yàn)榧鲜菙?shù)據(jù)結(jié)構(gòu),主要目的是以特定的時(shí)間/空間復(fù)雜度存儲(chǔ)和訪問元素(如ArrayList).但流的目的在于表達(dá)計(jì)算,如filter,sorted和map.集合講的是數(shù)據(jù),流講的是計(jì)算
- 源: 流會(huì)使用一個(gè)提供數(shù)據(jù)的源,如集合,數(shù)組或輸入/輸出資源
- 數(shù)據(jù)處理操作 流的數(shù)據(jù)處理功能支持類似于數(shù)據(jù)庫的操作,以及函數(shù)式編程語言中的常用操作,如filter,map,reduce,find,match,sort等。流操作可以順序執(zhí)行,也可并行執(zhí)行。
- 流與集合
- 粗糙地說,集合與流之間的差異就在于什么時(shí)候進(jìn)行計(jì)算。集合是一個(gè)內(nèi)存中的數(shù)據(jù)結(jié)構(gòu),它包含數(shù)據(jù)結(jié)構(gòu)中目前所有的值---集合中的每個(gè)元素都得先算出來才能添加到集合中。相比之下,流則是在概念上固定的數(shù)據(jù)結(jié)構(gòu)(你不能添加或刪除元素),其元素則是按需計(jì)算的。
- 只能遍歷一次 和迭代器一樣,流只能遍歷一次。遍歷完之后,我們就說這個(gè)流已經(jīng)被消費(fèi)掉了。
- 外部迭代與內(nèi)部迭代 Streams庫使用內(nèi)部迭代---它幫你把迭代做了,還把得到的流值存在了某個(gè)地方
- 流操作 java.util.stream.Stream中的Stream接口定義了許多操作。它們可以分為兩大類。
- 中間操作 如filter或sorted等中間操作會(huì)返回另一個(gè)流。這讓多個(gè)操作可以連接起來形成一個(gè)查詢。
- 終端操作 終端操作會(huì)從流的流水線生成結(jié)果。其結(jié)果是任何不是流的值,如List,Integer,甚至void
- 使用流 一般三件事:1--一個(gè)數(shù)據(jù)源來執(zhí)行一個(gè)查詢。 2--一個(gè)中間操作鏈,形成一條流的流水線 3--一個(gè)終端操作,執(zhí)行流水線,并能生成結(jié)果。
使用流
- 篩選和切片
- 用謂詞篩選 Stream接口支持filter方法。該操作會(huì)接受一個(gè)謂詞(一個(gè)返回boolean的函數(shù))作為參數(shù),并返回一個(gè)包括所有符合謂詞的元素的流。
- 篩選各異的元素 流還支持一個(gè)叫作distinct的方法,它會(huì)返回一個(gè)元素各異(根據(jù)元素的上hashcode和equels方法)的流。
- 截?cái)嗔? 流支持limit(n)方法,會(huì)返回一個(gè)不超過給定長(zhǎng)度的流。
- 跳過元素 流還支持skip(n)方法,返回一個(gè)扔掉了前n個(gè)元素的流。如果流中元素不足n個(gè),則返回一個(gè)空流。
- 映射 一個(gè)非常常見的數(shù)據(jù)處理套路就是從某些對(duì)象中選擇信息。比如在SQL里,你可以從表里選擇一列。Stream API也通過map和flatMap方法提供了類似的工具。
- 對(duì)流中每一個(gè)元素應(yīng)用函數(shù) 流支持map方法,它會(huì)接受一個(gè)函數(shù)作為參數(shù)。這個(gè)函數(shù)會(huì)被應(yīng)用到每個(gè)元素上,并將映射成一個(gè)新的元素
- 流的扁平化 flatmap方法讓你把一個(gè)流中的每個(gè)值都換成另一個(gè)流,然后把所有的流連接起來成為一個(gè)流。
- 查找和匹配 Stream API通過allMatch,anyMatch,noneMatch,findFirst和findAny方法提供了這樣的工具。
檢查謂詞是否至少匹配一個(gè)元素 anyMatch方法
檢查謂詞是否匹配所有元素 allMatch
noneMatch 流中沒有任何元素與給定的謂詞匹配
查找元素 findAny方法將返回當(dāng)前流中的任意元素。
-
Optional<T>類是一個(gè)容器類,代表一個(gè)值存在或不存在。Java8的庫設(shè)計(jì)人員引入了Optional<T>,這樣就不用返回眾所周知容易出問題的null了??此膸讉€(gè)方法:
--ifPresent() 將在Optional包含值的時(shí)候返回ture,否則false.
--ifPresent(Consumer<T> block) 會(huì)在值存在的時(shí)候執(zhí)行給定的代碼塊。
--T get() 會(huì)在值存在時(shí)返回值,否則拋出一個(gè)NoSuchElement異常
--T orElse(T other)會(huì)在值存在時(shí)返回值,否則返回一個(gè)默認(rèn)值。 查找第一個(gè)元素 findFirst
何時(shí)使用findFirst和findAny ,為什么會(huì)同時(shí)有findFirst和findAny?答案是并行。找到第一個(gè)元素在并行上限制更多。如果你不關(guān)心返回的元素是哪個(gè),請(qǐng)使用findAny()
- 歸約 如何把一個(gè)流中的元素組合起來,使用reduce操作來表達(dá)更復(fù)雜的查詢,如“計(jì)算菜單中的總卡路里”或“菜單中卡路里最高的菜是哪一個(gè)”。此類查詢需要將流中所有元素反復(fù)結(jié)合起來,得到一個(gè)值,比如一個(gè)Integer.這樣的查詢可以被歸類為歸約操作(將流歸約成一個(gè)值)。用函數(shù)式編程語言的術(shù)語來說,這稱為折疊(fold)
- 數(shù)值流
- 原始類型流特化。 Java8引入了三個(gè)原始類型特化流接口來解決一般裝箱拆箱問題:intStream,DoubleStream和LongStream,分別將流中的元素特化為int,long和double,從而避免了暗含的裝箱成本。每個(gè)接口都帶來了進(jìn)行常用數(shù)值歸約的新方法,如sum,max
---1. 映射到數(shù)值流,將流轉(zhuǎn)化為特化版本的常用方法是mapToInt,mapToDouble和mapToLong。這些方法和前面說的map方法的工作方式一樣,只是它們返回的是一個(gè)特化流,而不是Stream<T>
---2. 轉(zhuǎn)換回對(duì)象流 有了數(shù)值流,你可能會(huì)想把它轉(zhuǎn)換回非特化流。可以用boxed方法
---3. 默認(rèn)值OptionlInt。 Optional可以用Integer,String等參考類型來參數(shù)化。對(duì)于三種原始流特化,也分別有一個(gè)Optional原始類型特化版本:OptionalInt,OptionalDouble和OptionalLong.
- 數(shù)值范圍 和數(shù)字打交道時(shí),有一個(gè)常用的東西就是數(shù)值范圍,如,假設(shè)你想要生成1和100之間的所有數(shù)字。Java8引入了兩個(gè)可以用于IntStream和LongStream的靜態(tài)方法,幫助生成這種范圍:rang和的rangeClosed.這兩個(gè)方法都是第一參數(shù)接受起始值,第二個(gè)參數(shù)接受結(jié)束值。但range是不包含結(jié)束值的,而rangeClosed則包含結(jié)束值。
- 構(gòu)建流
-
由值創(chuàng)建流
可以使用靜態(tài)方法Stream.of 通過顯式值創(chuàng)建一個(gè)流。它可以接受任意數(shù)量的參數(shù)。
-
由數(shù)組創(chuàng)建流
你可以使用靜態(tài)方法Arrays.stream從數(shù)組創(chuàng)建一個(gè)流。它接受一個(gè)數(shù)組作為參數(shù)。
-
由文件生成流
java中用于處理文件等I/O操作的NIO API已更新,以便利用Stream API. java.nio.file.Files中很多靜態(tài)方法都會(huì)返回一個(gè)流。 如Files.lines,它會(huì)返回一上由指定文件中的各行構(gòu)成的字符串流。
-
由函數(shù)生成的流:創(chuàng)建無限流
Stream API提供了兩個(gè)靜態(tài)方法來從函數(shù)生成流:Stream.iterate和Stream.generate. 這兩個(gè)操作可以創(chuàng)建所謂的無限流:不像從固定集合創(chuàng)建的流那樣有固定大小的流。由iterate和generate產(chǎn)生的流會(huì)用給定的函數(shù)按需創(chuàng)建值,因此可以無窮地計(jì)算下去!一般來說,應(yīng)該使用limit(n)來對(duì)這種流加以限制。
---迭代 如:Stream.iterate(0,n->n+2)。 生成偶數(shù)流。一般來說,在需要依次生成一系列值的時(shí)候應(yīng)該使用iterate.
---生成 與iterate方法類似,generate方法也可讓你按需生成一個(gè)無限流,但generate不是依次每個(gè)新生成的值應(yīng)用函數(shù)的。它接受一個(gè)Supplier<T>類型的Lambda提供新的值。如 Stream.generate(Math::random)
用流收集數(shù)據(jù)
- 收集器簡(jiǎn)介
- 收集器用作高級(jí)歸約
收集器非常有用,用它可以簡(jiǎn)潔而靈活地定義collect用來生成的集合的標(biāo)準(zhǔn)。一般來說,Collector會(huì)元素應(yīng)用一個(gè)轉(zhuǎn)換函數(shù)(很多時(shí)候是不體現(xiàn)任何效果的恒等轉(zhuǎn)換,如toList),并將結(jié)果累積到一個(gè)數(shù)據(jù)結(jié)構(gòu)中,從而產(chǎn)生這一過程的最終輸出。 - 預(yù)定義收集器
預(yù)定義收集器也就是那些從Collectors類提供的工廠方法(如groupBy)創(chuàng)建的收集器。它們主要提供三大功能:--將流元素歸約和匯總為一個(gè)值 --元素分組 --元素分區(qū)
- 歸約和匯總
查找流中最大值和最小值 Collectors.maxBy和Colectors.minBy,來計(jì)算流中的最大和最小值。這兩個(gè)收集器接收一個(gè)Comparator參數(shù)來比較流中的元素。
匯總 Collecgors類專門為匯總提供了一個(gè)工廠方法:Collectors.summingInt。它可接受一個(gè)把對(duì)象映射為求和所需要int的函數(shù),并返回一個(gè)收集器。該收集器傳遞給普通的collect方法后即可執(zhí)行我們需要的匯總操作。 Collectors.summingLong和Collectors.summingDouble方法的作用完全一樣,可用于求和字段為long和double的情況。 但匯總不僅僅是求和:還有Collectors.averagingInt,連同對(duì)應(yīng)的averagingLong和averaingDouble可以計(jì)算數(shù)值的平均數(shù)。 目前為止,你已經(jīng)看到了如何使用收集器來給流中的元素計(jì)數(shù),找到這些元素?cái)?shù)值屬性的最大值和最小值,以及計(jì)算其總和和平均值。不過很多時(shí)候,你可能想要得到兩個(gè)或更多這樣的的結(jié)果,而且你希望只需要一次操作就可以完成。在這種情況下,可以使用summarizingInt工廠方法返回的收集器。如:IntSummaryStatistics menuStatistics = menu.stream().collect(summarizingIng(Dish::getCalories));
這個(gè)收集器會(huì)把所有這些信息收集到一個(gè)叫作IntSummaryStatistics類里,它提供了方便的取值(getter)方法來訪問結(jié)果。連接字符串 joining工廠方法返回的收集器會(huì)把流中每一個(gè)對(duì)象應(yīng)用toString方法得到所有字符串連接成一個(gè)字符串。 joining內(nèi)部使用了StringBuilder來把生成的字符串逐個(gè)追加起來。但該字符串的可讀性并不好,joining工廠方法有一個(gè)重載版本可以接受元素分界符joining(",").
到目前為止,我們已經(jīng)探討了各種將流歸約互一個(gè)值的收集器。下一節(jié),我們會(huì)展示為什么所有這種形式的歸約過程,其實(shí)都是Collectors.reducing工廠方法提供的更廣義歸約收集器的特殊情況。廣義的歸約匯總
事實(shí)上,我們已經(jīng)討論的所有收集器,都是一個(gè)可以用reducing工廠方法定義的歸納過程的特殊情況而已。Collectors.reducing工廠方法是所有這些特殊情況的一般化。 它需要三個(gè)參數(shù)
第一個(gè)參數(shù)是歸約操作的起始值,也中流中沒有元素時(shí)的返回值,所以很顯然對(duì)于數(shù)值和而言0是一個(gè)合適的值
第二個(gè)參數(shù)就是一個(gè)轉(zhuǎn)化函數(shù),如將菜肴轉(zhuǎn)化成一個(gè)表示其所含熱量的int
第三個(gè)參數(shù)是一個(gè)BinaryOperator,將兩個(gè)項(xiàng)目累加成一個(gè)同類型的值。如兩個(gè)int的和
同樣,你可以使用下面的單參數(shù)形式的reducing來找到熱量最高的菜。如下所示:
Optional<Dish> mostCalorieDish =menu.stream().collect(reducing((d1,d2)->d1.getCalories()>d2.getCalories()?d1:d2));
可以把單參數(shù)reducing工廠方法創(chuàng)建的收集器看作三參數(shù)方法的特殊情況,它把流中的第一個(gè)項(xiàng)目作為起點(diǎn),把恒等函數(shù)(即一個(gè)函數(shù)僅僅是返回其輸入?yún)?shù))作為一個(gè)轉(zhuǎn)換函數(shù)。這也意味著,要是把單參數(shù)reducing收集器傳遞給空流的collect方法,收集器就沒有起點(diǎn)。它將因此返回一個(gè)Optional<Dish>對(duì)象
從邏輯上說,歸約操作的工作原理:利用累積函數(shù),把一個(gè)初始化為起始值的累加器,和轉(zhuǎn)換函數(shù)應(yīng)用到流中每個(gè)元素上得到的結(jié)果不斷迭代合并起來。
- 分組
一個(gè)常見的數(shù)據(jù)為操作是根據(jù)一個(gè)或多個(gè)屬性對(duì)集合中的項(xiàng)目進(jìn)行分組。用Collectors.groupingBy工廠方法返回的收集器就可以輕松地完成這項(xiàng)任務(wù)。groupBy接收一個(gè)分類函數(shù),用它來把流中的元素分成不同的組。把分組函數(shù)返回的值作為映射的鍵,把流中所有具有這個(gè)分類值的項(xiàng)目的列表作為對(duì)應(yīng)的映射值。值就是包含所有對(duì)應(yīng)類型的列表。
- 多級(jí)分組
要實(shí)現(xiàn)多級(jí)分組,我們可以使用一個(gè)由雙參數(shù)版本的Collectors.groupingBy工廠方法創(chuàng)建的收集器,它除了普通的分類函數(shù)之外,還可以接受collector類型的第二個(gè)參數(shù),那么要時(shí)行二級(jí)分組的話,我們可以把內(nèi)層groupBy傳遞給外層groupingBy,并定義一個(gè)為流中項(xiàng)目分類的二級(jí)標(biāo)準(zhǔn)。二級(jí)分組的結(jié)果是兩級(jí)map.
- 按子組收集數(shù)據(jù)
上面的小節(jié),可以把第二個(gè)groupingBy收集器傳遞給外層收集器來實(shí)現(xiàn)多級(jí)分組。但進(jìn)一步說,傳遞給第一個(gè)groupingBy的第二個(gè)收集器可以是任何類型,而不一定是另一個(gè)groupingBy. 如,要數(shù)一數(shù)菜單中每類菜有多少個(gè),可以傳遞counting收集作為groupingBy收集器的第二個(gè)參數(shù)。
Map<Dish.Type,Long> typesCount = menu.stream().collect(groupingBy(Dish::getType,counting()));
注意普通的單參數(shù)groupingBy(f)(其中f是分類函數(shù))實(shí)際上是groupingBy(f,toList())的簡(jiǎn)便寫法。
---1. 把收集器的結(jié)果轉(zhuǎn)換為另一種類型。 因?yàn)榉纸M操作的Map結(jié)果中的每個(gè)值上包裝的Optional沒什么用,所 你可能想把它們?nèi)サ?。要做到這一點(diǎn),或者更一般地來說,把收集器返回的結(jié)果轉(zhuǎn)換為另一種類型,你可以使用Collectors.collectingAndThen工廠方法返回的收集器。
Map<Dish.Type, Dish> mostCaloricByType =
menu.stream()
.collect(groupingBy(Dish::getType,
collectingAndThen(
maxBy(comparingInt(Dish::getCalories)),
Optional::get)));
這個(gè)工廠方法接受兩個(gè)參數(shù)---要轉(zhuǎn)換的收集器以及轉(zhuǎn)換函數(shù),并返回另一個(gè)收集器。這個(gè)收集器相當(dāng)于舊收集器的一個(gè)包裝,collect操作的最后一步就是將返回值用轉(zhuǎn)換函數(shù)做一個(gè)映射。在這里,被包起來的收集器就是用maxBy建立的那個(gè),而轉(zhuǎn)換函數(shù)Optional::get則把返回的Optional中的值提取出來。這個(gè)操作放在這里是安全的,因?yàn)閞educing收集器永遠(yuǎn)不會(huì)返回Optional.empty().
把收集器嵌套起來很常見,它們從外層逐層向里有以下幾點(diǎn):
1. groupingBy是最外層,根據(jù)菜肴的類型把菜單流分組,得到 三個(gè)子流
2. groupingBy收集器包裹著collectingAndThen收集器,因此分組操作得到的每個(gè)子流都用這第二個(gè)收集器做進(jìn)一步歸約。
3. collectingAndThen收集器又包裹著第三個(gè)收集器maxBy
4. 隨后由歸約收集器進(jìn)行子流的歸約操作,然后包含它的collectingAndThen收集器會(huì)對(duì)其結(jié)果應(yīng)用Optional:get轉(zhuǎn)換函數(shù)
5. 對(duì)三個(gè)子流分別執(zhí)行這一過程并轉(zhuǎn)換而得到的三個(gè)值,也就是各個(gè)類型中熱量最高的Dish,將成為groupingBy收集器返回的Map中與各個(gè)分類鍵(Dish的類型)相關(guān)聯(lián)的值。
---2. 與groupingBy聯(lián)合使用的其他收集器的例子
通過groupingBy工廠方法的第二個(gè)參數(shù)傳遞的收集器將會(huì)對(duì)分到同一組中的所有流元素執(zhí)行進(jìn)一步歸約操作。如:你還重用求出所有菜肴熱量總和的收集器,不過這次是對(duì)每一組Dish求和:
Map<Dish.Type,Integer> totalCaloriesByType = menu.stream().collect(groupingBy(Dish::getType,summingInt(Dish::getCalories)));
然而常常和groupingBy聯(lián)合使用的另一個(gè)收集器是mapping方法生成的。這個(gè)方法接受兩個(gè)參數(shù):一個(gè)函數(shù)對(duì)流中的元素做變換,另一個(gè)則將變換的結(jié)果對(duì)象收集起來。其目的是在累加之前對(duì)每個(gè)輸入元素應(yīng)用一個(gè)映射函數(shù),這樣就要可以讓接受特定類型元素的收集器適應(yīng)不同類型的對(duì)象。 例子:比方說你想要知道,對(duì)于每種類型的Dish,菜單中都有哪些CaloricLevel.我們可以把groupingBy和mapping收集器結(jié)合起來,如下:
Map<Dish.Type,Set<CaloricLevel>> caloricLevelsByType =
menu.stream().collect(
goupingBy(Dish::getType,mapping( dish->{if (dish.getCalories<=400) return CaloricLevel.DIET; else if(dish.getCalories()<=700) return CaloricLevel.NORMAL; else return CaloricLevel.FAT;},
toSet())
)
);
- 分區(qū)
分區(qū)是分組的特殊情況:由一個(gè)謂詞(返回一個(gè)布爾值的函數(shù))作為分類函數(shù),它稱分區(qū)函數(shù)。分區(qū)函數(shù)返回一個(gè)布爾值,這意味著得到的分組Map的鍵類型是Boolean
- 分區(qū)的優(yōu)勢(shì)
分區(qū)的好處在于保留了分區(qū)函數(shù)返回true或false的兩套流元素列表。partitioningBy工廠方法還有一個(gè)重載版本,可以傳遞第第二個(gè)收集器
- 收集器接口
Collector接口包含了一系列方法,為實(shí)現(xiàn)具體的歸約操作(即收集器)提供了范本。
public interface Collector<T,A,R>{
Supplier<A> suppler();
BiConsumer<A,T> accumulator();
Function<A,R> finisher();
Set<Characteristics> characteristics();
}
本列表適用以下定義。
T是流中要收集的項(xiàng)目的泛型。
A是累加器的類型,累加器是在收集過程中用于累加部分結(jié)果的對(duì)象。
R是收集操作得到的對(duì)象(通常但并不一定是集合)的類型。
- 理解Collector接口聲明的方法
-
建立新的結(jié)果容器:supplier方法
supplier方法必須返回一個(gè)結(jié)果為空的Supplier,也就是一個(gè)無參數(shù)函數(shù),在調(diào)用時(shí)它會(huì)創(chuàng)建一個(gè)空的累加器實(shí)例,供數(shù)據(jù)收集過程使用。
-
將元素添加到結(jié)果容器:accumulator方法
accumulator方法會(huì)返回執(zhí)行歸約操作的函數(shù)。當(dāng)遍歷到流中第n個(gè)元素時(shí),這個(gè)函數(shù)執(zhí)行時(shí)會(huì)有兩個(gè)參數(shù):保存歸約結(jié)果的累加器(已收集了流中前n-1個(gè)項(xiàng)目),還有第n個(gè)元素本身。該函數(shù)將返回void,因?yàn)槔奂悠魇窃桓?,即函?shù)的執(zhí)行改變了它的內(nèi)部狀態(tài)以體現(xiàn)遍歷的元素的效果。
-
對(duì)結(jié)果容器應(yīng)用最終轉(zhuǎn)換:finisher方法
在遍歷完流后,finisher方法必須返回在累加過程的最后要調(diào)用的一個(gè)函數(shù),以便將累加器對(duì)象轉(zhuǎn)換為整個(gè)集合操作的最終結(jié)果。
這三個(gè)方法已經(jīng)足以對(duì)流進(jìn)行順序歸約。實(shí)踐中的實(shí)現(xiàn)細(xì)節(jié)可能還要復(fù)雜一點(diǎn),一方面是因?yàn)榱鞯难舆t性質(zhì),可能在collect操作之前還需要完成其他中間操作的流水線,另一方面則是理論上可能要進(jìn)行并行歸約。
-
合并兩個(gè)結(jié)果容器:combiner方法
四個(gè)方法中的最后一個(gè)-----combiner方法會(huì)返回一個(gè)供歸約操作使用的函數(shù),它定義了對(duì)流的各個(gè)子部分進(jìn)行并行處理時(shí),各個(gè)子部分歸約所得的累加器要如何合并。
有了這個(gè)第四個(gè)方法,就可以對(duì)流進(jìn)行并行歸約了。它會(huì)用到Java7中引入的分支/合并框架和Spliterator抽象。
-
characteristics方法
characteristics會(huì)返回一個(gè)不可變的Characteristics集合,它定義了收集器的行為----特別是關(guān)于流是否可以并行歸約,以及可以使用哪些優(yōu)化的提示。Characteristics是一個(gè)包含三個(gè)項(xiàng)目的枚舉:
---UNORDRED--歸約結(jié)果不受流中項(xiàng)目的遍歷和累積順序的影響
---CONCURRENT--accumulator函數(shù)可以從多個(gè)線程同時(shí)調(diào)用,且該收集器可以并行歸約流。如果收集器沒有標(biāo)為UNORDERED,那它僅在用于無序數(shù)據(jù)源時(shí)才可以并行歸約。
---IDENTITY_FINISH--這表明完成器方法返回的函數(shù)是一個(gè)恒等函數(shù),可以跳過。這種情況下,累加器對(duì)象將會(huì)直接用作歸約過程的最終結(jié)果。這也意味著,將累加器A不加檢查地轉(zhuǎn)換為結(jié)果R是安全的。
并行數(shù)據(jù)處理與性能
-
并行流
Stream接口可以通過收集源調(diào)用parallelStream方法來把集合轉(zhuǎn)換為并行流。并行流就是把一個(gè)內(nèi)容分成多個(gè)數(shù)據(jù)塊,并用不同的線程分別處理每個(gè)數(shù)據(jù)塊的流。
- 并行流內(nèi)部使用了默認(rèn)的ForkJoinPool,它默認(rèn)的線程數(shù)量就是你的處理器數(shù)量,這個(gè)值是由Runtime.getRunTime().availableProcessors()得到的。但是你可以通過系統(tǒng)屬性 java.util.concurrent.ForkJoinPool.common.parallelism來改變線程池大小。如下:
System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism","12");
使用正確的數(shù)據(jù)結(jié)構(gòu)然后使其并行化工作能保證最佳的性能。特別注意原始類型的裝箱和拆箱操作。
-
高效使用并行流
一些幫你決定某個(gè)特定情況下是否有必要使用并行流的建議:
- 有疑問,測(cè)量。把順序流轉(zhuǎn)化成并行流輕而易舉,但卻不一定是好事。并行流并不總是比順序流快。
- 留意裝箱。自動(dòng)裝箱和拆箱操作會(huì)大大降低性能。Java8中有原始類型流(IntStream,LongStream,DoubleStream)來避免這種操作,但凡有可能應(yīng)該使用這些流。
- 有些操作本身在并行流上的性能就比順序流差。特別是limit和findFirst等依賴于元素順序的操作。
- 還要考慮流的操作流水線的總計(jì)算成本。
- 對(duì)于較小的數(shù)據(jù)量,選擇并行幾乎從來都不是一個(gè)好的決定。并行處理少數(shù)幾個(gè)元素的好處還抵不上并行化造成的額外開銷。
- 要考慮流背后的數(shù)據(jù)結(jié)構(gòu)是否易于分解。如ArrayList的拆分效率比LinkList高得多,因?yàn)榍罢哂貌恢闅v就可以平均拆分,而后者則必須遍歷。另外,用range工廠方法創(chuàng)建的原始類型流也可以快速分解。
- 分支合并框架
分支合并框架的目的是以遞歸方式將可以并行的任務(wù)拆分成更小的任務(wù),然后將每個(gè)子任務(wù)的結(jié)果合并起來生成整體結(jié)果。這是ExecutorService接口的一個(gè)實(shí)現(xiàn),它把子任務(wù)分配給線程池(稱為ForkJoinPool)中的工作線程。
-
使用RecurisveTask
要把任務(wù)提交到這個(gè)池,必須創(chuàng)建RecursiveTask<R>的一個(gè)子類,其中R是并行化任務(wù)(以及所有子任務(wù))產(chǎn)生的結(jié)果類型,或者如果任務(wù)不返回結(jié)果,則是RecursiveAction類型(當(dāng)然它可能會(huì)更新其他非局部機(jī)構(gòu))。要定義RecursiveTask,只需要實(shí)現(xiàn)它唯一的抽象方法compute;
protected abstract R compute();
這個(gè)方法同時(shí)定義了將任務(wù)拆分成子任務(wù)的邏輯,以及無法再拆分或不方便再拆分時(shí),生成單個(gè)子任務(wù)結(jié)果的邏輯。偽代碼:if(任務(wù)足夠小或不可分){ 順序計(jì)算該任務(wù) }else{ 將任務(wù)分成兩個(gè)子任務(wù) 遞歸調(diào)用本方法,拆分每個(gè)子任務(wù),等待所有子任務(wù)完成 合并每個(gè)子任務(wù)的結(jié)果 }
選個(gè)例子為基礎(chǔ),讓我們?cè)囍眠@人框架為一個(gè)數(shù)字范圍(這里用一個(gè)long[]數(shù)組表示)求和。你需要先為RecursiveTask類做一個(gè)實(shí)現(xiàn):
package com.tim.test;
public class ForkJoinSumCalculator
extends java.util.concurrent.RecursiveTask<Long> {
private final long[] numbers;
private final int start;
private final int end;
public static final long THRESHOLD = 10_000;
public ForkJoinSumCalculator(long[] numbers) {
this(numbers, 0, numbers.length);
}
private ForkJoinSumCalculator(long[] numbers, int start, int end) {
this.numbers = numbers;
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
int length = end - start;
if (length <= THRESHOLD) {
return computeSequentially();
}
ForkJoinSumCalculator leftTask =
new ForkJoinSumCalculator(numbers, start, start + length / 2);
leftTask.fork();
ForkJoinSumCalculator rightTask =
new ForkJoinSumCalculator(numbers, start + length / 2, end);
Long rightResult = rightTask.compute();
Long leftResult = leftTask.join();
return leftResult + rightResult;
}
private long computeSequentially() {
long sum = 0;
for (int i = start; i < end; i++) {
{
sum += numbers[i];
}
return sum;
}
}
測(cè)試方法:
public static long forkJoinSum(long n) {
long[] numbers = LongStream.rangeClosed(1, n).toArray();
ForkJoinTask<Long> task = new ForkJoinSumCalculator(numbers);
return new ForkJoinPool().invoke(task);
}
運(yùn)行ForkJoinSumCalculator 當(dāng)把ForkJoinSumCalculator任務(wù)傳給ForkJoinPool時(shí),這個(gè)任務(wù)就由池中的一個(gè)線程執(zhí)行,這個(gè)線程會(huì)調(diào)用任務(wù)的compute方法。該方法會(huì)檢查任務(wù)是否小到足以順序執(zhí)行,如果不夠小則會(huì)把要求和的數(shù)組分成兩半,分給兩個(gè)新的ForkJoinSumCalculator,而它們也由ForkJoinPool安排執(zhí)行。因此,這一過程可以遞歸重復(fù),把原任務(wù)分為更小的任務(wù),直到滿足不方便或不可能再進(jìn)一步拆分的條件。這時(shí)會(huì)順序計(jì)算每個(gè)任務(wù)的結(jié)果,然后由分支過程創(chuàng)建的(隱含的)任務(wù)二叉樹遍歷回到它的根。接下來會(huì)合并每個(gè)子任務(wù)的部分結(jié)果,從而得到總?cè)蝿?wù)的結(jié)果。
-
使用分支合并框架的最佳做法
雖然分支/合并框架還算簡(jiǎn)單易用,但它容易被誤用。以下是幾個(gè)有效使用它的最佳做法:
- 對(duì)一個(gè)任務(wù)調(diào)用join方法會(huì)阻塞調(diào)用方,直到該任務(wù)做出結(jié)果。因此,有必要在兩個(gè)子任務(wù)的計(jì)算開始之后再調(diào)用它。否則,你得到的版本會(huì)比原始的順序算法更慢更復(fù)雜,因?yàn)槊總€(gè)子任務(wù)都必須等待另一個(gè)子任務(wù)完成才能啟動(dòng)。
- 不應(yīng)該在RecursieTask內(nèi)部使用ForkJoinPool的invoke方法。相反,應(yīng)該始終直接調(diào)用compute或fork方法,只有順序代碼才應(yīng)該用invoke來啟動(dòng)并行計(jì)算。
- 對(duì)子任務(wù)調(diào)用fork方法可以把它排進(jìn)ForkJoinPool。同時(shí)對(duì)左邊和右邊的子任務(wù)調(diào)用它似乎很自然,但這樣做的效率要比直接對(duì)其中一個(gè)調(diào)用compute低。這樣做你可以為其中一個(gè)子任務(wù)重用同一線程,從而避免在線程池中多分配一個(gè)任務(wù)造成的開銷。
- 調(diào)試使用分支/合并框架的并行計(jì)算可能有點(diǎn)棘手。特別是你平常都在喜歡的IDE里看棧跟蹤來找問題,但放在分支、合并計(jì)算上就不行了,因?yàn)檎{(diào)用compute的線程并不是概念上的調(diào)用方,后者是調(diào)用fork的那個(gè)。
- 和并行流一樣,你不應(yīng)理所當(dāng)然地認(rèn)為在多核處理器上使用分支合并計(jì)算比順序計(jì)算快。一個(gè)任務(wù)可以分解成多個(gè)獨(dú)立的子任務(wù),才能讓性能在并行化時(shí)有所提升。所有這些子任務(wù)的運(yùn)行時(shí)間都應(yīng)該比分出新任務(wù)所花的時(shí)間長(zhǎng);一個(gè)慣用方法是把輸入/輸出放在一個(gè)子任務(wù)里,計(jì)算放在另一個(gè)里,這樣計(jì)算就可以和輸入/輸出同時(shí)進(jìn)行。此外,在比較同一算法的順序和并行版本的性能時(shí)還有別的因素要考慮。就像任何其他Java代碼一樣,分支/合并框架需要“預(yù)熱”或者說要執(zhí)行幾遍才會(huì)被JIT編譯器優(yōu)化。這就是為什么在測(cè)量性能之前跑幾遍程序很重要,我們的測(cè)試框架就是這么
做的。同時(shí)還要知道,編譯器內(nèi)置的優(yōu)化可能會(huì)為順序版本帶來一些優(yōu)勢(shì)(例如執(zhí)行死碼分析——?jiǎng)h去從未被使用的計(jì)算)。
-
工作竊取
分支/合并框架工程用一種稱為工作竊取的技術(shù)解決這個(gè)問題。在實(shí)際應(yīng)用中,這意味著這些任務(wù)差不多被平均分配到ForkJoinPool中的所有線程上。每個(gè)線程都為分配給它的任務(wù)保存一個(gè)雙向鏈?zhǔn)疥?duì)列,每完成一個(gè)任務(wù),就會(huì)從隊(duì)列頭上取出下一個(gè)任務(wù)開始執(zhí)行?;谝恍┰?,某個(gè)線程可能早早完成了分配給它的任務(wù),也就是它的隊(duì)列已經(jīng)空了,而其它的線程還很忙。這時(shí),這個(gè)線程并沒有閑下來,而是隨機(jī)選了一個(gè)別的線程從隊(duì)列的尾巴上‘偷走’一個(gè)任務(wù)。這個(gè)過程一直繼續(xù)下去,直到所有的任務(wù)都執(zhí)行完畢,所有的隊(duì)列都清空。這就是為什么要?jiǎng)澇稍S多小任務(wù)而不是少數(shù)幾個(gè)大任務(wù),這有助于更好地工作線程之間平衡負(fù)載。
-
Spliterator
Spliterator是Java 8中加入的另一個(gè)新接口;這個(gè)名字代表“可分迭代器”(splitable
iterator)。和Iterator一樣,Spliterator也用于遍歷數(shù)據(jù)源中的元素,但它是為了并行執(zhí)行而設(shè)計(jì)的。雖然在實(shí)踐中可能用不著自己開發(fā)Spliterator,但了解一下它的實(shí)現(xiàn)方式會(huì)讓你對(duì)并行流的工作原理有更深入的了解。Java8已經(jīng)為集合框架中包含的所有數(shù)據(jù)結(jié)構(gòu)提供了一個(gè)默認(rèn)的Spliterator實(shí)現(xiàn)。集合實(shí)現(xiàn)了Spliterator接口,接口提供了一個(gè)spliterator方法。這個(gè)接口定義了若干方法,如下面的代碼清單所示。
public interface Spliterator<T> {
boolean tryAdvance(Consumer<? super T> action);
Spliterator<T> trySplit();
long estimateSize();
int characteristics();
}
與往常一樣,T是Spliterator遍歷的元素的類型。tryAdvance方法的行為類似于普通的因?yàn)樗鼤?huì)按順序一個(gè)一個(gè)使用Spliterator中的元素,并且如果還有其他元素要遍歷就返回true。但trySplit是專為Spliterator接口設(shè)計(jì)的,因?yàn)樗梢园岩恍┰貏澇鋈シ纸o第二個(gè)Spliterator(由該方法返回),讓它們兩個(gè)并行處理。Spliterator還可通過estimateSize方法估計(jì)還剩下多少元素要遍歷,因?yàn)榧词共荒敲创_切,能快速算出來是一個(gè)值也有助于讓拆分均勻一點(diǎn)。
高效Java8編程
重構(gòu),測(cè)試和調(diào)試
- 為改善可讀性和靈活性重構(gòu)代碼
- 改善代碼的可讀性
利用Lambda表達(dá)式,方法引用以及Stream改善代碼的可讀性:
--重構(gòu)代碼,用Lambda表達(dá)式取代匿名類
--用方法引用重構(gòu)Lambda表達(dá)式
--用Stream API重構(gòu)命令式的數(shù)據(jù)處理。
默認(rèn)方法
Java8允許在接口內(nèi)聲明靜態(tài)方法。Java8引入了一個(gè)新功能,叫默認(rèn)方法,通過默認(rèn)方法可以指定接口方法的默認(rèn)實(shí)現(xiàn)。換句話說,接口能提供方法的具體實(shí)現(xiàn)。因此,實(shí)現(xiàn)接口的類如果不顯式地提供該方法的具體實(shí)現(xiàn),就會(huì)自動(dòng)繼承默認(rèn)的實(shí)現(xiàn)。這種機(jī)制可以使你平滑地進(jìn)行接口的優(yōu)化和演進(jìn)。
解決默認(rèn)方法沖突的三條原則:
- 類中的方法優(yōu)先級(jí)最高。類或父類中聲明的方法的優(yōu)先級(jí)高于任何聲明為默認(rèn)方法的優(yōu)先級(jí)
- 如果無法依據(jù)第一條進(jìn)行判斷,那么子接口的優(yōu)先級(jí)更高:函數(shù)簽名相同時(shí),優(yōu)先選擇擁有最具體實(shí)現(xiàn)的默認(rèn)方法的接口,即如果B繼承了A,那么B就比A更具體。
- 最后,如果還是無法判斷,繼承了多個(gè)接口的類必須通過顯式覆蓋和調(diào)用期望的方法,顯式地選擇使用哪一個(gè)默認(rèn)方法的實(shí)現(xiàn) 。Java8 引入了一種新的語法X.super.m(...),其中x是你希望調(diào)用的m方法的父接口
用Optional取代null
- 創(chuàng)建Optional對(duì)象
- 聲明一個(gè)空的Optional
可以通過靜態(tài)工廠方法Optional.empty,創(chuàng)建一個(gè)空的Optional對(duì)象:Optional<Car> optCar = Optional.empty()
- 依據(jù)一個(gè)非空值創(chuàng)建Optional
使得靜態(tài)工廠方法Optional.of,依據(jù)一個(gè)非空值創(chuàng)建一個(gè)Optional對(duì)象:
Optional<Car> optcar=Optional.of(car)
- 可接受null的Optional
使用靜態(tài)工廠方法Optional.ofNullable,你可以創(chuàng)建一個(gè)允許null值的Optional
對(duì)象:
Optional<Car> optCar = Optional.ofNullable(car);
如果car是null,那么得到的Optional對(duì)象就是個(gè)空對(duì)象。
completablerFuture組合式異步編程
-
future接口
它建模了一種異步計(jì)算,返回一個(gè)執(zhí)行運(yùn)算結(jié)果的引用,當(dāng)運(yùn)算結(jié)束后,這個(gè)引用被返回給調(diào)用方。要使用Future,通常你只需要將耗時(shí)的操作封裝在一個(gè)Callable對(duì)象中,再將它提交給ExecutorService,就萬事大吉了
使用CompletableFuture構(gòu)建異步應(yīng)用。
使用supplyAsync創(chuàng)建CompletableFuture對(duì)象。
Java 8的 CompletableFuture API提供了名為thenCompose的方法,它就是專門為這一目的而設(shè)計(jì)的,thenCompose方法允許你對(duì)兩個(gè)異步操作進(jìn)行流水線,第一個(gè)操作完成時(shí),將其結(jié)果作為參數(shù)傳遞給第二個(gè)操作。換句話說,你可以創(chuàng)建兩個(gè)CompletableFutures對(duì)象,對(duì)第一個(gè)CompletableFuture對(duì)象調(diào)用thenCompose,并向其傳遞一個(gè)函數(shù)。當(dāng)?shù)谝粋€(gè)CompletableFuture執(zhí)行完畢后,它的結(jié)果將作為該函數(shù)的參數(shù),這個(gè)函數(shù)的返回值是以第一個(gè)CompletableFuture的返回做輸入計(jì)算出的第二個(gè)CompletableFuture對(duì)象。
CompletableFuture利用Lambda表達(dá)式以聲明式的API提供了一種機(jī)制,能夠用最有效的方式,
非常容易地將多個(gè)以同步或異步方式執(zhí)行復(fù)雜操作的任務(wù)結(jié)合到一起
一等函數(shù)是可以作為參數(shù)傳遞,可以作為結(jié)果返回,同時(shí)還能存儲(chǔ)在數(shù)據(jù)結(jié)構(gòu)中的函數(shù)。
? 高階函數(shù)接受至少一個(gè)或者多個(gè)函數(shù)作為輸入?yún)?shù),或者返回另一個(gè)函數(shù)的函數(shù)。Java
中典型的高階函數(shù)包括comparing、andThen和compose。
? 科里化是一種幫助你模塊化函數(shù)和重用代碼的技術(shù)。