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