摘要:但有一個(gè)限制它們不能修改定義的方法的局部變量的內(nèi)容。如前所述,這種限制存在的原因在于局部變量保存在棧上,并且隱式表示它們僅限于其所在線程。
2014年,Oracle發(fā)布了Java8新版本。對(duì)于Java來(lái)說(shuō),這顯然是一個(gè)具有里程碑意義的版本。尤其是那函數(shù)式編程的功能,避開(kāi)了Java那煩瑣的語(yǔ)法所帶來(lái)的麻煩。
這可以算是一篇Java8的學(xué)習(xí)筆記。將Java8一些常見(jiàn)的一些特性作了一個(gè)概要的筆記。
行為參數(shù)化(Lambda以及方法引用)為了編寫可重用的方法,比如filter,你需要為其指定一個(gè)參數(shù),它能夠精確地描述過(guò)濾條件。雖然Java專家們使用之前的版本也能達(dá)到同樣的目的(將過(guò)濾條件封裝成類的一個(gè)方法,傳遞該類的一個(gè)實(shí)例),但這種方案卻很難推廣,因?yàn)樗ǔ7浅S纺[,既難于編寫,也不易于維護(hù)。
Java 8通過(guò)借鑒函數(shù)式編程,提供了一種新的方式——通過(guò)向方法傳遞代碼片段來(lái)解決這一問(wèn)題。這種新的方法非常方便地提供了兩種變體。
傳遞一個(gè)Lambda表達(dá)式,即一段精簡(jiǎn)的代碼片段,比如
apple -> apple.getWeight() > 150
傳遞一個(gè)方法引用,該方法引用指向了一個(gè)現(xiàn)有的方法,比如這樣的代碼:
Apple::isHeavy
這些值具有類似Function
閉包函數(shù)接口
你可能已經(jīng)聽(tīng)說(shuō)過(guò)閉包(closure,不要和Clojure編程語(yǔ)言混淆)這個(gè)詞,你可能會(huì)想Lambda是否滿足閉包的定義。用科學(xué)的說(shuō)法來(lái)說(shuō),閉包就是一個(gè)函數(shù)的實(shí)例,且它可以無(wú)限制地訪問(wèn)那個(gè)函數(shù)的非本地變量。例如,閉包可以作為參數(shù)傳遞給另一個(gè)函數(shù)。它也可以訪問(wèn)和修改其作用域之外的變量?,F(xiàn)在,Java 8的Lambda和匿名類可以做類似于閉包的事情:它們可以作為參數(shù)傳遞給方法,并且可以訪問(wèn)其作用域之外的變量。但有一個(gè)限制:它們不能修改定義Lambda的方法的局部變量的內(nèi)容。這些變量必須是隱式最終的??梢哉J(rèn)為L(zhǎng)ambda是對(duì)值封閉,而不是對(duì)變量封閉。如前所述,這種限制存在的原因在于局部變量保存在棧上,并且隱式表示它們僅限于其所在線程。如果允許捕獲可改變的局部變量,就會(huì)引發(fā)造成線程不安全的新的可能性,而這是我們不想看到的(實(shí)例變量可以,因?yàn)樗鼈儽4嬖诙阎?,而堆是在線程之間共享的)。
Java 8之前,接口主要用于定義方法簽名,現(xiàn)在它們還能為接口的使用者提供方法的默認(rèn)實(shí)現(xiàn),如果接口的設(shè)計(jì)者認(rèn)為接口中聲明的某個(gè)方法并不需要每一個(gè)接口的用戶顯式地提供實(shí)現(xiàn),他就可以考慮在接口的方法聲明中為其定義默認(rèn)方法。
對(duì)類庫(kù)的設(shè)計(jì)者而言,這是個(gè)偉大的新工具,原因很簡(jiǎn)單,它提供的能力能幫助類庫(kù)的設(shè)計(jì)者們定義新的操作,增強(qiáng)接口的能力,類庫(kù)的用戶們(即那些實(shí)現(xiàn)該接口的程序員們)不需要花費(fèi)額外的精力重新實(shí)現(xiàn)該方法。因此,默認(rèn)方法與庫(kù)的用戶也有關(guān)系,它們屏蔽了將來(lái)的變化對(duì)用戶的影響。
在接口上添加注解:@FunctionalInterface。即可聲明該接口為函數(shù)接口。
如果你去看看新的Java API,會(huì)發(fā)現(xiàn)函數(shù)式接口帶有@FunctionalInterface的標(biāo)注。這個(gè)標(biāo)注用于表示該接口會(huì)設(shè)計(jì)成一個(gè)函數(shù)式接口。如果你用@FunctionalInterface定義了一個(gè)接口,而它卻不是函數(shù)式接口的話,編譯器將返回一個(gè)提示原因的錯(cuò)誤。例如,錯(cuò)誤消息可能是“Multiple non-overriding abstract methods found in interface Foo”,表明存在多個(gè)抽象方法。請(qǐng)注意,@FunctionalInterface不是必需的,但對(duì)于為此設(shè)計(jì)的接口而言,使用它是比較好的做法。它就像是@Override標(biāo)注表示方法被重寫了。
Lambdas及函數(shù)式接口的例子:
使用案例 | Lambda例子 | 對(duì)應(yīng)的函數(shù)式接口 |
---|---|---|
布爾表達(dá)式 | (List |
Predicate
|
創(chuàng)建對(duì)象 | () -> new Apple(10) | Supplier |
消費(fèi)一個(gè)對(duì)象 | (Apple a) ->System.out.println(a.getWeight()) | Consumer |
從一個(gè)對(duì)象中選擇/提取 | (String s) -> s.length() | Function |
合并兩個(gè)值 | (int a, int b) -> a * b | IntBinaryOperator |
比較兩個(gè)對(duì)象 | (Apple a1, Apple a2) ->a1.getWeight().compareTo(a2.getWeight()) | Comparator |
要討論流,我們先來(lái)談?wù)劶?,這是最容易上手的方式了。Java 8中的集合支持一個(gè)新的stream方法,它會(huì)返回一個(gè)流(接口定義在java.util.stream.Stream里)。你在后面會(huì)看到,還有很多其他的方法可以得到流,比如利用數(shù)值范圍或從I/O資源生成流元素。
那么,流到底是什么呢?簡(jiǎn)短的定義就是“從支持?jǐn)?shù)據(jù)處理操作的源生成的元素序列”。讓我們一步步剖析這個(gè)定義。
元素序列——就像集合一樣,流也提供了一個(gè)接口,可以訪問(wèn)特定元素類型的一組有序值。因?yàn)榧鲜菙?shù)據(jù)結(jié)構(gòu),所以它的主要目的是以特定的時(shí)間/空間復(fù)雜度存儲(chǔ)和訪問(wèn)元素(如ArrayList 與 LinkedList)。但流的目的在于表達(dá)計(jì)算,比如你前面見(jiàn)到的filter、sorted和map。集合講的是數(shù)據(jù),流講的是計(jì)算。我們會(huì)在后面幾節(jié)中詳細(xì)解釋這個(gè)思想。
源——流會(huì)使用一個(gè)提供數(shù)據(jù)的源,如集合、數(shù)組或輸入/輸出資源。 請(qǐng)注意,從有序集合生成流時(shí)會(huì)保留原有的順序。由列表生成的流,其元素順序與列表一致。
數(shù)據(jù)處理操作——流的數(shù)據(jù)處理功能支持類似于數(shù)據(jù)庫(kù)的操作,以及函數(shù)式編程語(yǔ)言中的常用操作,如filter、map、reduce、find、match、sort等。流操作可以順序執(zhí)行,也可并行執(zhí)行。
此外,流操作有兩個(gè)重要的特點(diǎn)。
流水線——很多流操作本身會(huì)返回一個(gè)流,這樣多個(gè)操作就可以鏈接起來(lái),形成一個(gè)大的流水線。這讓我們下一章中的一些優(yōu)化成為可能,如延遲和短路。流水線的操作可以看作對(duì)數(shù)據(jù)源進(jìn)行數(shù)據(jù)庫(kù)式查詢。
內(nèi)部迭代——與使用迭代器顯式迭代的集合不同,流的迭代操作是在背后進(jìn)行的。
流與集合Java現(xiàn)有的集合概念和新的流概念都提供了接口,來(lái)配合代表元素型有序值的數(shù)據(jù)接口。所謂有序,就是說(shuō)我們一般是按順序取用值,而不是隨機(jī)取用的。那這兩者有什么區(qū)別呢?
我們先來(lái)打個(gè)直觀的比方吧。比如說(shuō)存在DVD里的電影,這就是一個(gè)集合(也許是字節(jié),也許是幀,這個(gè)無(wú)所謂),因?yàn)樗苏麄€(gè)數(shù)據(jù)結(jié)構(gòu)。現(xiàn)在再來(lái)想想在互聯(lián)網(wǎng)上通過(guò)視頻流看同樣的電影?,F(xiàn)在這是一個(gè)流(字節(jié)流或幀流)。流媒體視頻播放器只要提前下載用戶觀看位置的那幾幀就可以了,這樣不用等到流中大部分值計(jì)算出來(lái),你就可以顯示流的開(kāi)始部分了(想想觀看直播足球賽)。特別要注意,視頻播放器可能沒(méi)有將整個(gè)流作為集合,保存所需要的內(nèi)存緩沖區(qū)——而且要是非得等到最后一幀出現(xiàn)才能開(kāi)始看,那等待的時(shí)間就太長(zhǎng)了。出于實(shí)現(xiàn)的考慮,你也可以讓視頻播放器把流的一部分緩存在集合里,但和概念上的差異不是一回事。
粗略地說(shuō),集合與流之間的差異就在于什么時(shí)候進(jìn)行計(jì)算。集合是一個(gè)內(nèi)存中的數(shù)據(jù)結(jié)構(gòu),它包含數(shù)據(jù)結(jié)構(gòu)中目前所有的值——集合中的每個(gè)元素都得先算出來(lái)才能添加到集合中。(你可以往集合里加?xùn)|西或者刪東西,但是不管什么時(shí)候,集合中的每個(gè)元素都是放在內(nèi)存里的,元素都得先算出來(lái)才能成為集合的一部分。)
相比之下,流則是在概念上固定的數(shù)據(jù)結(jié)構(gòu)(你不能添加或刪除元素),其元素則是按需計(jì)算的。 這對(duì)編程有很大的好處。在第6章中,我們將展示構(gòu)建一個(gè)質(zhì)數(shù)流(2, 3, 5, 7, 11, …)有多簡(jiǎn)單,盡管質(zhì)數(shù)有無(wú)窮多個(gè)。這個(gè)思想就是用戶僅僅從流中提取需要的值,而這些值——在用戶看不見(jiàn)的地方——只會(huì)按需生成。這是一種生產(chǎn)者-消費(fèi)者的關(guān)系。從另一個(gè)角度來(lái)說(shuō),流就像是一個(gè)延遲創(chuàng)建的集合:只有在消費(fèi)者要求的時(shí)候才會(huì)計(jì)算值(用管理學(xué)的話說(shuō)這就是需求驅(qū)動(dòng),甚至是實(shí)時(shí)制造)。
與此相反,集合則是急切創(chuàng)建的(供應(yīng)商驅(qū)動(dòng):先把倉(cāng)庫(kù)裝滿,再開(kāi)始賣,就像那些曇花一現(xiàn)的圣誕新玩意兒一樣)。以質(zhì)數(shù)為例,要是想創(chuàng)建一個(gè)包含所有質(zhì)數(shù)的集合,那這個(gè)程序算起來(lái)就沒(méi)完沒(méi)了了,因?yàn)榭傆行碌馁|(zhì)數(shù)要算,然后把它加到集合里面。當(dāng)然這個(gè)集合是永遠(yuǎn)也創(chuàng)建不完的,消費(fèi)者這輩子都見(jiàn)不著了。
流的操作操作 | 類型 | 返回類型 | 使用的類型、函數(shù)式接口 | 函數(shù)描述符 |
---|---|---|---|---|
filter | 中間 | Stream |
Predicate |
T -> boolean |
distinct | 中間(有狀態(tài)-無(wú)界) | Stream |
`` | `` |
skip | 中間(有狀態(tài)-有界) | Stream |
long | `` |
limit | 中間(有狀態(tài)-有界) | Stream |
long | `` |
map | 中間 | Stream |
Function |
T -> R |
flatMap | 中間 | Stream |
Function |
T -> Stream |
sorted | 中間(有狀態(tài)-無(wú)界) | Stream |
Comparator |
(T, T) -> int |
anyMatch | 終端 | boolean | Predicate |
T -> boolean |
noneMatch | 終端 | boolean | Predicate |
T -> boolean |
allMatch | 終端 | boolean | Predicate |
T -> boolean |
findAny | 終端 | Optional |
`` | `` |
findFirst | 終端 | Optional |
`` | `` |
forEach | 終端 | void | Consumer |
T -> void |
collect | 終端 | R | Collector |
`` |
reduce`` | 終端(有狀態(tài)-有界) | Optional |
BinaryOperator |
(T, T)-> T |
count | 終端 | long | `` | `` |
即Collectors類提供的工廠方法(例如groupingBy)創(chuàng)建的收集器。它們主要提供了三大功能:
將流元素歸約和匯總為一個(gè)值
元素分組
元素分區(qū)
Collectors類的靜態(tài)工廠方法
工廠方法 | 返回類型 | 用于 |
---|---|---|
toList | List |
把流中所有項(xiàng)目收集到一個(gè)List |
使用示例:
Listdishes = menuStream.collect(toList());
工廠方法 | 返回類型 | 用于 |
---|---|---|
toSet | Set |
把流中所有項(xiàng)目收集到一個(gè)Set,刪除重復(fù)項(xiàng) |
使用示例:
Setdishes = menuStream.collect(toSet());
工廠方法 | 返回類型 | 用于 |
---|---|---|
toCollection | Collection |
把流中所有項(xiàng)目收集到給定的供應(yīng)源創(chuàng)建的集合 |
使用示例:
Collectiondishes = menuStream.collect(toCollection(),ArrayList::new);
工廠方法 | 返回類型 | 用于 |
---|---|---|
counting | Long | 計(jì)算流中元素的個(gè)數(shù) |
使用示例:
long howManyDishes = menuStream.collect(counting());
工廠方法 | 返回類型 | 用于 |
---|---|---|
summingInt | Integer | 對(duì)流中項(xiàng)目的一個(gè)整數(shù)屬性求和 |
使用示例:
int totalCalories = menuStream.collect(summingInt(Dish::getCalories));
工廠方法 | 返回類型 | 用于 |
---|---|---|
averagingInt | Double | 計(jì)算流中項(xiàng)目Integer屬性的平均值 |
使用示例:
double avgCalories = menuStream.collect(averagingInt(Dish::getCalories));
工廠方法 | 返回類型 | 用于 |
---|---|---|
summarizingInt | IntSummaryStatistics | 收集關(guān)于流中項(xiàng)目Integer屬性的統(tǒng)計(jì)值,例如最大、最小、總和與平均值 |
使用示例:
IntSummaryStatistics menuStatistics = menuStream.collect(summarizingInt(Dish::getCalories));
工廠方法 | 返回類型 | 用于 |
---|---|---|
joining` | String | 連接對(duì)流中每個(gè)項(xiàng)目調(diào)用toString方法所生成的字符串 |
使用示例:
String shortMenu = menuStream.map(Dish::getName).collect(joining(", "));
工廠方法 | 返回類型 | 用于 |
---|---|---|
maxBy | Optional |
一個(gè)包裹了流中按照給定比較器選出的最大元素的Optional,或如果流為空則為Optional.empty() |
使用示例:
Optionalfattest = menuStream.collect(maxBy(comparingInt(Dish::getCalories)));
工廠方法 | 返回類型 | 用于 |
---|---|---|
minBy | Optional |
一個(gè)包裹了流中按照給定比較器選出的最小元素的Optional,或如果流為空則為Optional.empty() |
使用示例:
Optionallightest = menuStream.collect(minBy(comparingInt(Dish::getCalories)));
工廠方法 | 返回類型 | 用于 |
---|---|---|
reducing | 歸約操作產(chǎn)生的類型 | 從一個(gè)作為累加器的初始值開(kāi)始,利用BinaryOperator與流中的元素逐個(gè)結(jié)合,從而將流歸約為單個(gè)值 |
使用示例:
int totalCalories = menuStream.collect(reducing(0, Dish::getCalories, Integer::sum));
工廠方法 | 返回類型 | 用于 |
---|---|---|
collectingAndThen | 轉(zhuǎn)換函數(shù)返回的類型 | 包裹另一個(gè)收集器,對(duì)其結(jié)果應(yīng)用轉(zhuǎn)換函數(shù) |
使用示例:
int howManyDishes = menuStream.collect(collectingAndThen(toList(), List::size));
工廠方法 | 返回類型 | 用于 |
---|---|---|
groupingBy | Map |
根據(jù)項(xiàng)目的一個(gè)屬性的值對(duì)流中的項(xiàng)目作問(wèn)組,并將屬性值作為結(jié)果Map的鍵 |
使用示例:
Map> dishesByType = menuStream.collect(groupingBy(Dish::getType));
工廠方法 | 返回類型 | 用于 |
---|---|---|
partitioningBy | Map |
根據(jù)對(duì)流中每個(gè)項(xiàng)目應(yīng)用謂詞的結(jié)果來(lái)對(duì)項(xiàng)目進(jìn)行分區(qū) |
使用示例:
Map并行流> vegetarianDishes = menuStream.collect(partitioningBy(Dish::isVegetarian));
在Java 7之前,并行處理數(shù)據(jù)集合非常麻煩。第一,你得明確地把包含數(shù)據(jù)的數(shù)據(jù)結(jié)構(gòu)分成若干子部分。第二,你要給每個(gè)子部分分配一個(gè)獨(dú)立的線程。第三,你需要在恰當(dāng)?shù)臅r(shí)候?qū)λ鼈冞M(jìn)行同步來(lái)避免不希望出現(xiàn)的競(jìng)爭(zhēng)條件,等待所有線程完成,最后把這些部分結(jié)果合并起來(lái)。Java 7引入了一個(gè)叫作分支/合并的框架,讓這些操作更穩(wěn)定、更不易出錯(cuò)。
我們簡(jiǎn)要地提到了Stream接口可以讓你非常方便地處理它的元素:可以通過(guò)對(duì)收集源調(diào)用parallelStream方法來(lái)把集合轉(zhuǎn)換為并行流。并行流就是一個(gè)把內(nèi)容分成多個(gè)數(shù)據(jù)塊,并用不同的線程分別處理每個(gè)數(shù)據(jù)塊的流。這樣一來(lái),你就可以自動(dòng)把給定操作的工作負(fù)荷分配給多核處理器的所有內(nèi)核,讓它們都忙起來(lái)。
高效使用并行流一般而言,想給出任何關(guān)于什么時(shí)候該用并行流的定量建議都是不可能也毫無(wú)意義的,因?yàn)槿魏晤愃朴凇皟H當(dāng)至少有一千個(gè)(或一百萬(wàn)個(gè)或隨便什么數(shù)字)元素的時(shí)候才用并行流)”的建議對(duì)于某臺(tái)特定機(jī)器上的某個(gè)特定操作可能是對(duì)的,但在略有差異的另一種情況下可能就是大錯(cuò)特錯(cuò)。盡管如此,我們至少可以提出一些定性意見(jiàn),幫你決定某個(gè)特定情況下是否有必要使用并行流。
如果有疑問(wèn),測(cè)量。把順序流轉(zhuǎn)成并行流輕而易舉,但卻不一定是好事。我們?cè)诒竟?jié)中已經(jīng)指出,并行流并不總是比順序流快。此外,并行流有時(shí)候會(huì)和你的直覺(jué)不一致,所以在考慮選擇順序流還是并行流時(shí),第一個(gè)也是最重要的建議就是用適當(dāng)?shù)幕鶞?zhǔn)來(lái)檢查其性能。
留意裝箱。自動(dòng)裝箱和拆箱操作會(huì)大大降低性能。Java 8中有原始類型流(IntStream、LongStream、DoubleStream)來(lái)避免這種操作,但凡有可能都應(yīng)該用這些流。
有些操作本身在并行流上的性能就比順序流差。特別是limit和findFirst等依賴于元素順序的操作,它們?cè)诓⑿辛魃蠄?zhí)行的代價(jià)非常大。例如,findAny會(huì)比f(wàn)indFirst性能好,因?yàn)樗灰欢ㄒ错樞騺?lái)執(zhí)行。你總是可以調(diào)用unordered方法來(lái)把有序流變成無(wú)序流。那么,如果你需要流中的n 個(gè)元素而不是專門要前n 個(gè)的話,對(duì)無(wú)序并行流調(diào)用limit可能會(huì)比單個(gè)有序流(比如數(shù)據(jù)源是一個(gè)List)更高效。
還要考慮流的操作流水線的總計(jì)算成本。設(shè) N 是要處理的元素的總數(shù),Q 是一個(gè)元素通過(guò)流水線的大致處理成本,則 N*Q 就是這個(gè)對(duì)成本的一個(gè)粗略的定性估計(jì)。Q 值較高就意味著使用并行流時(shí)性能好的可能性比較大。
對(duì)于較小的數(shù)據(jù)量,選擇并行流幾乎從來(lái)都不是一個(gè)好的決定。并行處理少數(shù)幾個(gè)元素的好處還抵不上并行化造成的額外開(kāi)銷。
要考慮流背后的數(shù)據(jù)結(jié)構(gòu)是否易于分解。例如,ArrayList的拆分效率比LinkedList高得多,因?yàn)榍罢哂貌恢闅v就可以平均拆分,而后者則必須遍歷。另外,用range工廠方法創(chuàng)建的原始類型流也可以快速分解。
流自身的特點(diǎn),以及流水線中的中間操作修改流的方式,都可能會(huì)改變分解過(guò)程的性能。例如,一個(gè)SIZED流可以分成大小相等的兩部分,這樣每個(gè)部分都可以比較高效地并行處理,但篩選操作可能丟棄的元素個(gè)數(shù)卻無(wú)法預(yù)測(cè),導(dǎo)致流本身的大小未知。
還要考慮終端操作中合并步驟的代價(jià)是大是?。ɡ鏑ollector中的combiner方法)。如果這一步代價(jià)很大,那么組合每個(gè)子流產(chǎn)生的部分結(jié)果所付出的代價(jià)就可能會(huì)超出通過(guò)并行流得到的性能提升。
流的數(shù)據(jù)源和可分解性源 | 可分解性 |
---|---|
ArrayList | 極佳 |
LinkedList | 差 |
IntStream.range | 極佳 |
Stream.iterate | 差 |
HashSet | 好 |
TreeSet | 好 |
Java 8的庫(kù)提供了Optional
方法 | 描述 |
---|---|
empty | 返回一個(gè)空的Optional實(shí)例 |
filter | 如果值存在并且滿足提供的謂詞,就返回包含該值的Optional對(duì)象;否則返回一個(gè)空的Optional對(duì)象 |
flatMap | 如果值存在,就對(duì)該值執(zhí)行提供的mapping函數(shù)調(diào)用,返回一個(gè)Optional類型的值,否則就返回一個(gè)空的Optional對(duì)象 |
get | 如果該值存在,將該值用Optional封裝返回,否則拋出一個(gè)NoSuchElementException異常 |
ifPresent | 如果值存在,就執(zhí)行使用該值的方法調(diào)用,否則什么也不做 |
isPresent | 如果值存在就返回true,否則返回false |
map | 如果值存在,就對(duì)該值執(zhí)行提供的mapping函數(shù)調(diào)用 |
of | 將指定值用Optional封裝之后返回,如果該值為null,則拋出一個(gè)NullPointerException異常 |
ofNullable | 將指定值用Optional封裝之后返回,如果該值為null,則返回一個(gè)空的Optional對(duì)象 |
orElse | 如果有值則將其返回,否則返回一個(gè)默認(rèn)值 |
orElseGet | 如果有值則將其返回,否則返回一個(gè)由指定的Supplier接口生成的值 |
orElseThrow | 如果有值則將其返回,否則拋出一個(gè)由指定的Supplier接口生成的異常 |
null引用在歷史上被引入到程序設(shè)計(jì)語(yǔ)言中,目的是為了表示變量值的缺失。
Java 8中引入了一個(gè)新的類java.util.Optional
你可以使用靜態(tài)工廠方法Optional.empty、Optional.of以及Optional.ofNullable創(chuàng)建Optional對(duì)象。
Optional類支持多種方法,比如map、flatMap、filter,它們?cè)诟拍钌吓cStream類中對(duì)應(yīng)的方法十分相似。
使用Optional會(huì)迫使你更積極地解引用Optional對(duì)象,以應(yīng)對(duì)變量值缺失的問(wèn)題,最終,你能更有效地防止代碼中出現(xiàn)不期而至的空指針異常。
使用Optional能幫助你設(shè)計(jì)更好的API,用戶只需要閱讀方法簽名,就能了解該方法是否接受一個(gè)Optional類型的值。
CompletableFutureJava從Java 5版本就提供了Future接口。Future對(duì)于充分利用多核處理能力是非常有益的,因?yàn)樗试S一個(gè)任務(wù)在一個(gè)新的核上生成一個(gè)新的子線程,新生成的任務(wù)可以和原來(lái)的任務(wù)同時(shí)運(yùn)行。原來(lái)的任務(wù)需要結(jié)果時(shí),它可以通過(guò)get方法等待Future運(yùn)行結(jié)束(生成其計(jì)算的結(jié)果值)。
Future接口的局限性我們知道Future接口提供了方法來(lái)檢測(cè)異步計(jì)算是否已經(jīng)結(jié)束(使用isDone方法),等待異步操作結(jié)束,以及獲取計(jì)算的結(jié)果。但是這些特性還不足以讓你編寫簡(jiǎn)潔的并發(fā)代碼。比如,我們很難表述Future結(jié)果之間的依賴性;從文字描述上這很簡(jiǎn)單,“當(dāng)長(zhǎng)時(shí)間計(jì)算任務(wù)完成時(shí),請(qǐng)將該計(jì)算的結(jié)果通知到另一個(gè)長(zhǎng)時(shí)間運(yùn)行的計(jì)算任務(wù),這兩個(gè)計(jì)算任務(wù)都完成后,將計(jì)算的結(jié)果與另一個(gè)查詢操作結(jié)果合并”。但是,使用Future中提供的方法完成這樣的操作又是另外一回事。這也是我們需要更具描述能力的特性的原因,比如下面這些。
將兩個(gè)異步計(jì)算合并為一個(gè)——這兩個(gè)異步計(jì)算之間相互獨(dú)立,同時(shí)第二個(gè)又依賴于第一個(gè)的結(jié)果。
等待Future集合中的所有任務(wù)都完成。
僅等待Future集合中最快結(jié)束的任務(wù)完成(有可能因?yàn)樗鼈冊(cè)噲D通過(guò)不同的方式計(jì)算同一個(gè)值),并返回它的結(jié)果。
通過(guò)編程方式完成一個(gè)Future任務(wù)的執(zhí)行(即以手工設(shè)定異步操作結(jié)果的方式)。
應(yīng)對(duì)Future的完成事件(即當(dāng)Future的完成事件發(fā)生時(shí)會(huì)收到通知,并能使用Future計(jì)算的結(jié)果進(jìn)行下一步的操作,不只是簡(jiǎn)單地阻塞等待操作的結(jié)果)。
CompletableFuture 詳解
一個(gè)非常有用,不過(guò)不那么精確的格言這么說(shuō):“Completable-Future對(duì)于Future的意義就像Stream之于Collection?!弊屛覀儽容^一下這二者。
通過(guò)Stream你可以對(duì)一系列的操作進(jìn)行流水線,通過(guò)map、filter或者其他類似的方法提供行為參數(shù)化,它可有效避免使用迭代器時(shí)總是出現(xiàn)模板代碼。
類似地,CompletableFuture提供了像thenCompose、thenCombine、allOf這樣的操作,對(duì)Future涉及的通用設(shè)計(jì)模式提供了函數(shù)式編程的細(xì)粒度控制,有助于避免使用命令式編程的模板代碼。
新的日期和時(shí)間APIJava的API提供了很多有用的組件,能幫助你構(gòu)建復(fù)雜的應(yīng)用。不過(guò),Java API也不總是完美的。我們相信大多數(shù)有經(jīng)驗(yàn)的程序員都會(huì)贊同Java 8之前的庫(kù)對(duì)日期和時(shí)間的支持就非常不理想。然而,你也不用太擔(dān)心:Java 8中引入全新的日期和時(shí)間API就是要解決這一問(wèn)題。
LocalDate
LocalTime
LocalDateTime
Instant
Duration
Period
使用LocalDate和LocalTime還有LocalDateTime開(kāi)始使用新的日期和時(shí)間API時(shí),你最先碰到的可能是LocalDate類。該類的實(shí)例是一個(gè)不可變對(duì)象,它只提供了簡(jiǎn)單的日期,并不含當(dāng)天的時(shí)間信息。另外,它也不附帶任何與時(shí)區(qū)相關(guān)的信息。
你可以通過(guò)靜態(tài)工廠方法of創(chuàng)建一個(gè)LocalDate實(shí)例。LocalDate實(shí)例提供了多種方法來(lái)讀取常用的值,比如年份、月份、星期幾等,如下所示。
LocalDate date = LocalDate.of(2014, 3, 18); ←─2014-03-18 int year = date.getYear(); ←─2014 Month month = date.getMonth(); ←─MARCH int day = date.getDayOfMonth(); ←─18 DayOfWeek dow = date.getDayOfWeek(); ←─TUESDAY int len = date.lengthOfMonth(); ←─31 (days in March) boolean leap = date.isLeapYear(); ←─false (not a leap year) //你還可以使用工廠方法從系統(tǒng)時(shí)鐘中獲取當(dāng)前的日期: LocalDate today = LocalDate.now();
LocalTime和LocalDateTime都提供了類似的方法。
機(jī)器的日期和時(shí)間格式作為人,我們習(xí)慣于以星期幾、幾號(hào)、幾點(diǎn)、幾分這樣的方式理解日期和時(shí)間。毫無(wú)疑問(wèn),這種方式對(duì)于計(jì)算機(jī)而言并不容易理解。從計(jì)算機(jī)的角度來(lái)看,建模時(shí)間最自然的格式是表示一個(gè)持續(xù)時(shí)間段上某個(gè)點(diǎn)的單一大整型數(shù)。這也是新的java.time.Instant類對(duì)時(shí)間建模的方式,基本上它是以Unix元年時(shí)間(傳統(tǒng)的設(shè)定為UTC時(shí)區(qū)1970年1月1日午夜時(shí)分)開(kāi)始所經(jīng)歷的秒數(shù)進(jìn)行計(jì)算。
你可以通過(guò)向靜態(tài)工廠方法ofEpochSecond傳遞一個(gè)代表秒數(shù)的值創(chuàng)建一個(gè)該類的實(shí)例。靜態(tài)工廠方法ofEpochSecond還有一個(gè)增強(qiáng)的重載版本,它接收第二個(gè)以納秒為單位的參數(shù)值,對(duì)傳入作為秒數(shù)的參數(shù)進(jìn)行調(diào)整。重載的版本會(huì)調(diào)整納秒?yún)?shù),確保保存的納秒分片在0到999 999 999之間。這意味著下面這些對(duì)ofEpochSecond工廠方法的調(diào)用會(huì)返回幾乎同樣的Instant對(duì)象:
Instant.ofEpochSecond(3); Instant.ofEpochSecond(3, 0); Instant.ofEpochSecond(2, 1_000_000_000); ←─2 秒之后再加上100萬(wàn)納秒(1秒) Instant.ofEpochSecond(4, -1_000_000_000); ←─4秒之前的100萬(wàn)納秒(1秒)
正如你已經(jīng)在LocalDate及其他為便于閱讀而設(shè)計(jì)的日期-時(shí)間類中所看到的那樣,Instant類也支持靜態(tài)工廠方法now,它能夠幫你獲取當(dāng)前時(shí)刻的時(shí)間戳。我們想要特別強(qiáng)調(diào)一點(diǎn),Instant的設(shè)計(jì)初衷是為了便于機(jī)器使用。它包含的是由秒及納秒所構(gòu)成的數(shù)字。所以,它無(wú)法處理那些我們非常容易理解的時(shí)間單位。比如下面這段語(yǔ)句:
int day = Instant.now().get(ChronoField.DAY_OF_MONTH);
它會(huì)拋出下面這樣的異常:
java.time.temporal.UnsupportedTemporalTypeException: Unsupported field:
DayOfMonth
但是你可以通過(guò)Duration和Period類使用Instant,接下來(lái)我們會(huì)對(duì)這部分內(nèi)容進(jìn)行介紹。
定義Duration或Period目前為止,你看到的所有類都實(shí)現(xiàn)了Temporal接口,Temporal接口定義了如何讀取和操縱為時(shí)間建模的對(duì)象的值。之前的介紹中,我們已經(jīng)了解了創(chuàng)建Temporal實(shí)例的幾種方法。很自然地你會(huì)想到,我們需要?jiǎng)?chuàng)建兩個(gè)Temporal對(duì)象之間的duration。Duration類的靜態(tài)工廠方法between就是為這個(gè)目的而設(shè)計(jì)的。你可以創(chuàng)建兩個(gè)LocalTimes對(duì)象、兩個(gè)LocalDateTimes對(duì)象,或者兩個(gè)Instant對(duì)象之間的duration,如下所示:
Duration d1 = Duration.between(time1, time2); Duration d1 = Duration.between(dateTime1, dateTime2); Duration d2 = Duration.between(instant1, instant2);
由于LocalDateTime和Instant是為不同的目的而設(shè)計(jì)的,一個(gè)是為了便于人閱讀使用,另一個(gè)是為了便于機(jī)器處理,所以你不能將二者混用。如果你試圖在這兩類對(duì)象之間創(chuàng)建duration,會(huì)觸發(fā)一個(gè)DateTimeException異常。此外,由于Duration類主要用于以秒和納秒衡量時(shí)間的長(zhǎng)短,你不能僅向between方法傳遞一個(gè)LocalDate對(duì)象做參數(shù)。
如果你需要以年、月或者日的方式對(duì)多個(gè)時(shí)間單位建模,可以使用Period類。使用該類的工廠方法between,你可以使用得到兩個(gè)LocalDate之間的時(shí)長(zhǎng),如下所示:
Period tenDays = Period.between(LocalDate.of(2014, 3, 8), LocalDate.of(2014, 3, 18));
最后,Duration和Period類都提供了很多非常方便的工廠類,直接創(chuàng)建對(duì)應(yīng)的實(shí)例;換句話說(shuō),就像下面這段代碼那樣,不再是只能以兩個(gè)temporal對(duì)象的差值的方式來(lái)定義它們的對(duì)象。
創(chuàng)建Duration和Period對(duì)象
Duration threeMinutes = Duration.ofMinutes(3); Duration threeMinutes = Duration.of(3, ChronoUnit.MINUTES); Period tenDays = Period.ofDays(10); Period threeWeeks = Period.ofWeeks(3); Period twoYearsSixMonthsOneDay = Period.of(2, 6, 1);
Duration類和Period類共享了很多相似的方法:
方法名 | 是否是靜態(tài)方法 | 方法描述 |
---|---|---|
between | 是 | 創(chuàng)建兩個(gè)時(shí)間點(diǎn)之間的interval |
from | 是 | 由一個(gè)臨時(shí)時(shí)間點(diǎn)創(chuàng)建interval |
of | 是 | 由它的組成部分創(chuàng)建interval的實(shí)例 |
parse | 是 | 由字符串創(chuàng)建interval的實(shí)例 |
addTo | 否 | 創(chuàng)建該interval的副本,并將其疊加到某個(gè)指定的temporal對(duì)象 |
get | 否 | 讀取該interval的狀態(tài) |
isNegative | 否 | 檢查該interval是否為負(fù)值,不包含零 |
isZero | 否 | 檢查該interval的時(shí)長(zhǎng)是否為零 |
minus | 否 | 通過(guò)減去一定的時(shí)間創(chuàng)建該interval的副本 |
multipliedBy | 否 | 將interval的值乘以某個(gè)標(biāo)量創(chuàng)建該interval的副本 |
negated | 否 | 以忽略某個(gè)時(shí)長(zhǎng)的方式創(chuàng)建該interval的副本 |
plus | 否 | 以增加某個(gè)指定的時(shí)長(zhǎng)的方式創(chuàng)建該interval的副本 |
subtractFrom | 否 | 從指定的temporal對(duì)象中減去該interval |
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/66043.html
摘要:種一顆樹(shù)最好的時(shí)機(jī)是十年前,其次是現(xiàn)在經(jīng)過(guò)一段刻骨的升本歷程,來(lái)到了西華大學(xué)。計(jì)劃是前進(jìn)的路線圖免除對(duì)于以后學(xué)習(xí)的各自夸大的計(jì)劃,從實(shí)際出發(fā)找到適合自己的前進(jìn)的路線圖。今年我歲,年輕。 種一顆樹(shù)最好的時(shí)機(jī)是十年前,其次是現(xiàn)在 經(jīng)過(guò)一段刻骨的升本歷程,來(lái)到了西華大學(xué)。明顯能感覺(jué)到自己又有了新的...
摘要:等待其安裝完成后關(guān)閉程序,重新啟動(dòng),點(diǎn)開(kāi)菜單可見(jiàn)項(xiàng),說(shuō)明插件管理包已安裝成功。在出現(xiàn)的懸浮對(duì)話框中輸入然后點(diǎn)選下面的插件,就會(huì)自動(dòng)開(kāi)始安裝,請(qǐng)耐心等待?!咀ⅲ阂韵聝?nèi)容參考https://blog.csdn.net/stilling2006/article/details/54376743】 一、認(rèn)識(shí)Sublime text 1、一款跨平臺(tái)代碼編輯器,在Linux、OSX和Windows下均可...
摘要:寫在前面最近在學(xué)習(xí),遇到有些頁(yè)面請(qǐng)求數(shù)據(jù)需要用戶登錄權(quán)限服務(wù)器響應(yīng)不符預(yù)期的問(wèn)題,但是總不能每個(gè)頁(yè)面都做單獨(dú)處理吧,于是想到提供了攔截器這個(gè)好東西,再于是就出現(xiàn)了本文。 1.寫在前面 最近在學(xué)習(xí)Vue2,遇到有些頁(yè)面請(qǐng)求數(shù)據(jù)需要用戶登錄權(quán)限、服務(wù)器響應(yīng)不符預(yù)期的問(wèn)題,但是總不能每個(gè)頁(yè)面都做單獨(dú)處理吧,于是想到axios提供了攔截器這個(gè)好東西,再于是就出現(xiàn)了本文。 2.具體需求 用戶鑒...
摘要:前段時(shí)間為了抓取網(wǎng)絡(luò)文本數(shù)據(jù),申請(qǐng)了騰訊云學(xué)生機(jī),用的框架弄了一段時(shí)間。這個(gè)用戶既是不可登錄的操作系統(tǒng)用戶,也是數(shù)據(jù)庫(kù)用戶。設(shè)置數(shù)據(jù)庫(kù)用戶密碼為了能夠讓和數(shù)據(jù)庫(kù)相連接,需要設(shè)置數(shù)據(jù)庫(kù)用戶密碼。 打讀研之后,更加關(guān)注算法的學(xué)習(xí),Web開(kāi)發(fā)這一塊便落下了,平時(shí)也通過(guò)微信公眾號(hào)關(guān)注了些,常常感慨,技術(shù)的更迭真是日新月異。 前段時(shí)間為了抓取網(wǎng)絡(luò)文本數(shù)據(jù),申請(qǐng)了騰訊云學(xué)生機(jī),用Python的Sc...
閱讀 947·2021-09-26 09:55
閱讀 3192·2021-09-22 15:36
閱讀 2982·2021-09-04 16:48
閱讀 3142·2021-09-01 11:41
閱讀 2591·2019-08-30 13:49
閱讀 1491·2019-08-29 18:46
閱讀 3546·2019-08-29 17:28
閱讀 3425·2019-08-29 14:11