轉載自https://segmentfault.com/a/1190000006985405java
2014年,Oracle發佈了Java8新版本。對於Java來講,這顯然是一個具備里程碑意義的版本。尤爲是那函數式編程的功能,避開了Java那煩瑣的語法所帶來的麻煩。程序員
這能夠算是一篇Java8的學習筆記。將Java8一些常見的一些特性做了一個概要的筆記。數據庫
爲了編寫可重用的方法,好比filter,你須要爲其指定一個參數,它可以精確地描述過濾條件。雖然Java專家們使用以前的版本也能達到一樣的目的(將過濾條件封裝成類的一個方法,傳遞該類的一個實例),但這種方案卻很難推廣,由於它一般很是臃腫,既難於編寫,也不易於維護。編程
Java 8經過借鑑函數式編程,提供了一種新的方式——經過向方法傳遞代碼片斷來解決這一問題。這種新的方法很是方便地提供了兩種變體。segmentfault
apple -> apple.getWeight() > 150
Apple::isHeavy
這些值具備相似Function<T, R>、Predicate<T>或者BiFunction<T, U, R>這樣的類型,值的接收方能夠經過apply、test或其餘相似的方法執行這些方法。Lambda表達式自身是一個至關酷炫的概念,不過Java 8對它們的使用方式——將它們與全新的Stream API相結合,最終把它們推向了新一代Java的核心。設計模式
閉包
你可能已經據說過閉包(closure,不要和Clojure編程語言混淆)這個詞,你可能會想Lambda是否知足閉包的定義。用科學的說法來講,閉包就是一個函數的實例,且它能夠無限制地訪問那個函數的非本地變量。例如,閉包能夠做爲參數傳遞給另外一個函數。它也能夠訪問和修改其做用域以外的變量。如今,Java 8的Lambda和匿名類能夠作相似於閉包的事情:它們能夠做爲參數傳遞給方法,而且能夠訪問其做用域以外的變量。但有一個限制:它們不能修改定義Lambda的方法的局部變量的內容。這些變量必須是隱式最終的。能夠認爲Lambda是對值封閉,而不是對變量封閉。如前所述,這種限制存在的緣由在於局部變量保存在棧上,而且隱式表示它們僅限於其所在線程。若是容許捕獲可改變的局部變量,就會引起形成線程不安全的新的可能性,而這是咱們不想看到的(實例變量能夠,由於它們保存在堆中,而堆是在線程之間共享的)。數組
Java 8以前,接口主要用於定義方法簽名,如今它們還能爲接口的使用者提供方法的默認實現,若是接口的設計者認爲接口中聲明的某個方法並不須要每個接口的用戶顯式地提供實現,他就能夠考慮在接口的方法聲明中爲其定義默認方法。緩存
對類庫的設計者而言,這是個偉大的新工具,緣由很簡單,它提供的能力能幫助類庫的設計者們定義新的操做,加強接口的能力,類庫的用戶們(即那些實現該接口的程序員們)不須要花費額外的精力從新實現該方法。所以,默認方法與庫的用戶也有關係,它們屏蔽了未來的變化對用戶的影響。第9章針對這一問題進行了更加深刻的探討。安全
在接口上添加註解:@FunctionalInterface。便可聲明該接口爲函數接口。數據結構
若是你去看看新的Java API,會發現函數式接口帶有@FunctionalInterface的標註。這個標註用於表示該接口會設計成一個函數式接口。若是你用@FunctionalInterface定義了一個接口,而它卻不是函數式接口的話,編譯器將返回一個提示緣由的錯誤。例如,錯誤消息多是「Multiple non-overriding abstract methods found in interface Foo」,代表存在多個抽象方法。請注意,@FunctionalInterface不是必需的,但對於爲此設計的接口而言,使用它是比較好的作法。它就像是@Override標註表示方法被重寫了。
Lambdas及函數式接口的例子:
使用案例 | Lambda例子 | 對應的函數式接口 |
---|---|---|
布爾表達式 | (List<String> list) -> list.isEmpty() |
Predicate<List<String>> |
建立對象 | () -> new Apple(10) |
Supplier<Apple> |
消費一個對象 | (Apple a) ->System.out.println(a.getWeight()) |
Consumer<Apple> |
從一個對象中選擇/提取 | (String s) -> s.length() |
Function<String, Integer>或ToIntFunction<String> |
合併兩個值 | (int a, int b) -> a * b |
IntBinaryOperator |
比較兩個對象 | (Apple a1, Apple a2) ->a1.getWeight().compareTo(a2.getWeight()) |
Comparator<Apple>或BiFunction<Apple, Apple, Integer>或ToIntBiFunction<Apple, Apple> |
要討論流,咱們先來談談集合,這是最容易上手的方式了。Java 8中的集合支持一個新的stream方法,它會返回一個流(接口定義在java.util.stream.Stream裏)。你在後面會看到,還有不少其餘的方法能夠獲得流,好比利用數值範圍或從I/O資源生成流元素。
那麼,流究竟是什麼呢?簡短的定義就是「從支持數據處理操做的源生成的元素序列」。讓咱們一步步剖析這個定義。
此外,流操做有兩個重要的特色。
Java現有的集合概念和新的流概念都提供了接口,來配合表明元素型有序值的數據接口。所謂有序,就是說咱們通常是按順序取用值,而不是隨機取用的。那這二者有什麼區別呢?
咱們先來打個直觀的比方吧。好比說存在DVD裏的電影,這就是一個集合(也許是字節,也許是幀,這個無所謂),由於它包含了整個數據結構。如今再來想一想在互聯網上經過視頻流看一樣的電影。如今這是一個流(字節流或幀流)。流媒體視頻播放器只要提早下載用戶觀看位置的那幾幀就能夠了,這樣不用等到流中大部分值計算出來,你就能夠顯示流的開始部分了(想一想觀看直播足球賽)。特別要注意,視頻播放器可能沒有將整個流做爲集合,保存所須要的內存緩衝區——並且要是非得等到最後一幀出現才能開始看,那等待的時間就太長了。出於實現的考慮,你也可讓視頻播放器把流的一部分緩存在集合裏,但和概念上的差別不是一回事。
粗略地說,集合與流之間的差別就在於何時進行計算。集合是一個內存中的數據結構,它包含數據結構中目前全部的值——集合中的每一個元素都得先算出來才能添加到集合中。(你能夠往集合里加東西或者刪東西,可是無論何時,集合中的每一個元素都是放在內存裏的,元素都得先算出來才能成爲集合的一部分。)
相比之下,流則是在概念上固定的數據結構(你不能添加或刪除元素),其元素則是按需計算的。 這對編程有很大的好處。在第6章中,咱們將展現構建一個質數流(2, 3, 5, 7, 11, …)有多簡單,儘管質數有無窮多個。這個思想就是用戶僅僅從流中提取須要的值,而這些值——在用戶看不見的地方——只會按需生成。這是一種生產者-消費者的關係。從另外一個角度來講,流就像是一個延遲建立的集合:只有在消費者要求的時候纔會計算值(用管理學的話說這就是需求驅動,甚至是實時製造)。
與此相反,集合則是急切建立的(供應商驅動:先把倉庫裝滿,再開始賣,就像那些曇花一現的聖誕新玩意兒同樣)。以質數爲例,要是想建立一個包含全部質數的集合,那這個程序算起來就沒完沒了了,由於總有新的質數要算,而後把它加到集合裏面。固然這個集合是永遠也建立不完的,消費者這輩子都見不着了。
操做 | 類型 | 返回類型 | 使用的類型、函數式接口 | 函數描述符 |
---|---|---|---|---|
filter |
中間 | Stream<T> |
Predicate<T> |
T -> boolean |
distinct |
中間(有狀態-無界) | Stream<T> |
`` | `` |
skip |
中間(有狀態-有界) | Stream<T> |
long |
`` |
limit |
中間(有狀態-有界) | Stream<T> |
long |
`` |
map |
中間 | Stream<R> |
Function<T, R> |
T -> R |
flatMap |
中間 | Stream<R> |
Function<T, Stream<R>> |
T -> Stream<R> |
sorted |
中間(有狀態-無界) | Stream<T> |
Comparator<T> |
(T, T) -> int |
anyMatch |
終端 | boolean |
Predicate<T> |
T -> boolean |
noneMatch |
終端 | boolean |
Predicate<T> |
T -> boolean |
allMatch |
終端 | boolean |
Predicate<T> |
T -> boolean |
findAny |
終端 | Optional<T> |
`` | `` |
findFirst |
終端 | Optional<T> |
`` | `` |
forEach |
終端 | void |
Consumer<T> |
T -> void |
collect |
終端 | R |
Collector<T, A, R> |
`` |
reduce`` | 終端(有狀態-有界) | Optional<T> |
BinaryOperator<T> |
(T, T)-> T |
count |
終端 | long |
`` | `` |
即Collectors
類提供的工廠方法(例如groupingBy)建立的收集器。它們主要提供了三大功能:
Collectors類的靜態工廠方法
工廠方法 | 返回類型 | 用於 |
---|---|---|
toList | List<T> | 把流中全部項目收集到一個List |
使用示例:
List<Dish> dishes = menuStream.collect(toList());
工廠方法 | 返回類型 | 用於 |
---|---|---|
toSet | Set<T> | 把流中全部項目收集到一個Set,刪除重複項 |
使用示例:
Set<Dish> dishes = menuStream.collect(toSet());
工廠方法 | 返回類型 | 用於 |
---|---|---|
toCollection | Collection<T> | 把流中全部項目收集到給定的供應源建立的集合 |
使用示例:
Collection<Dish> dishes = menuStream.collect(toCollection(),ArrayList::new);
工廠方法 | 返回類型 | 用於 |
---|---|---|
counting | Long | 計算流中元素的個數 |
使用示例:
long howManyDishes = menuStream.collect(counting());
工廠方法 | 返回類型 | 用於 |
---|---|---|
summingInt | Integer | 對流中項目的一個整數屬性求和 |
使用示例:
int totalCalories = menuStream.collect(summingInt(Dish::getCalories));
工廠方法 | 返回類型 | 用於 |
---|---|---|
averagingInt | Double | 計算流中項目Integer屬性的平均值 |
使用示例:
double avgCalories = menuStream.collect(averagingInt(Dish::getCalories));
工廠方法 | 返回類型 | 用於 |
---|---|---|
summarizingInt | IntSummaryStatistics | 收集關於流中項目Integer屬性的統計值,例如最大、最小、總和與平均值 |
使用示例:
IntSummaryStatistics menuStatistics = menuStream.collect(summarizingInt(Dish::getCalories));
工廠方法 | 返回類型 | 用於 |
---|---|---|
joining` | String | 鏈接對流中每一個項目調用toString方法所生成的字符串 |
使用示例:
String shortMenu =
menuStream.map(Dish::getName).collect(joining(", "));
工廠方法 | 返回類型 | 用於 |
---|---|---|
maxBy | Optional<T> | 一個包裹了流中按照給定比較器選出的最大元素的Optional,或若是流爲空則爲Optional.empty() |
使用示例:
Optional<Dish> fattest = menuStream.collect(maxBy(comparingInt(Dish::getCalories)));
工廠方法 | 返回類型 | 用於 |
---|---|---|
minBy | Optional<T> | 一個包裹了流中按照給定比較器選出的最小元素的Optional,或若是流爲空則爲Optional.empty() |
使用示例:
Optional<Dish> lightest = menuStream.collect(minBy(comparingInt(Dish::getCalories)));
工廠方法 | 返回類型 | 用於 |
---|---|---|
reducing | 歸約操做產生的類型 | 從一個做爲累加器的初始值開始,利用BinaryOperator與流中的元素逐個結合,從而將流歸約爲單個值 |
使用示例:
int totalCalories = menuStream.collect(reducing(0, Dish::getCalories, Integer::sum));
工廠方法 | 返回類型 | 用於 |
---|---|---|
collectingAndThen | 轉換函數返回的類型 | 包裹另外一個收集器,對其結果應用轉換函數 |
使用示例:
int howManyDishes = menuStream.collect(collectingAndThen(toList(), List::size));
工廠方法 | 返回類型 | 用於 |
---|---|---|
groupingBy | Map<K, List<T>> | 根據項目的一個屬性的值對流中的項目做問組,並將屬性值做爲結果Map 的鍵 |
使用示例:
Map<Dish.Type,List<Dish>> dishesByType = menuStream.collect(groupingBy(Dish::getType));
工廠方法 | 返回類型 | 用於 |
---|---|---|
partitioningBy | Map<Boolean,List<T>> | 根據對流中每一個項目應用謂詞的結果來對項目進行分區 |
使用示例:
Map<Boolean,List<Dish>> vegetarianDishes = menuStream.collect(partitioningBy(Dish::isVegetarian));
在Java 7以前,並行處理數據集合很是麻煩。第一,你得明確地把包含數據的數據結構分紅若干子部分。第二,你要給每一個子部分分配一個獨立的線程。第三,你須要在恰當的時候對它們進行同步來避免不但願出現的競爭條件,等待全部線程完成,最後把這些部分結果合併起來。Java 7引入了一個叫做分支/合併的框架,讓這些操做更穩定、更不易出錯。
咱們簡要地提到了Stream
接口可讓你很是方便地處理它的元素:能夠經過對收集源調用parallelStream
方法來把集合轉換爲並行流。並行流就是一個把內容分紅多個數據塊,並用不一樣的線程分別處理每一個數據塊的流。這樣一來,你就能夠自動把給定操做的工做負荷分配給多核處理器的全部內核,讓它們都忙起來。
通常而言,想給出任何關於何時該用並行流的定量建議都是不可能也毫無心義的,由於任何相似於「僅當至少有一千個(或一百萬個或隨便什麼數字)元素的時候才用並行流)」的建議對於某臺特定機器上的某個特定操做多是對的,但在略有差別的另外一種狀況下可能就是大錯特錯。儘管如此,咱們至少能夠提出一些定性意見,幫你決定某個特定狀況下是否有必要使用並行流。
源 | 可分解性 |
---|---|
ArrayList | 極佳 |
LinkedList | 差 |
IntStream.range | 極佳 |
Stream.iterate | 差 |
HashSet | 好 |
TreeSet | 好 |
Java 8的庫提供了Optional<T>類,這個類容許你在代碼中指定哪個變量的值既多是類型T的值,也多是由靜態方法Optional.empty表示的缺失值。不管是對於理解程序邏輯,抑或是對於編寫產品文檔而言,這都是一個重大的好消息,你如今能夠經過一種數據類型表示顯式缺失的值——使用空指針的問題在於你沒法確切瞭解出現空指針的緣由,它是預期的狀況,仍是說因爲以前的某一次計算出錯致使的一個偶然性的空值,有了Optional以後你就不須要再使用以前容易出錯的空指針來表示缺失的值了。
方法 | 描述 |
---|---|
empty | 返回一個空的Optional實例 |
filter | 若是值存在而且知足提供的謂詞,就返回包含該值的Optional對象;不然返回一個空的Optional對象 |
flatMap | 若是值存在,就對該值執行提供的mapping函數調用,返回一個Optional類型的值,不然就返回一個空的Optional對象 |
get | 若是該值存在,將該值用Optional封裝返回,不然拋出一個NoSuchElementException異常 |
ifPresent | 若是值存在,就執行使用該值的方法調用,不然什麼也不作 |
isPresent | 若是值存在就返回true,不然返回false |
map | 若是值存在,就對該值執行提供的mapping函數調用 |
of | 將指定值用Optional封裝以後返回,若是該值爲null,則拋出一個NullPointerException異常 |
ofNullable | 將指定值用Optional封裝以後返回,若是該值爲null,則返回一個空的Optional對象 |
orElse | 若是有值則將其返回,不然返回一個默認值 |
orElseGet | 若是有值則將其返回,不然返回一個由指定的Supplier接口生成的值 |
orElseThrow | 若是有值則將其返回,不然拋出一個由指定的Supplier接口生成的異常 |
Java從Java 5版本就提供了Future接口。Future對於充分利用多核處理能力是很是有益的,由於它容許一個任務在一個新的核上生成一個新的子線程,新生成的任務能夠和原來的任務同時運行。原來的任務須要結果時,它能夠經過get方法等待Future運行結束(生成其計算的結果值)。
咱們知道Future接口提供了方法來檢測異步計算是否已經結束(使用isDone方法),等待異步操做結束,以及獲取計算的結果。可是這些特性還不足以讓你編寫簡潔的併發代碼。好比,咱們很難表述Future結果之間的依賴性;從文字描述上這很簡單,「當長時間計算任務完成時,請將該計算的結果通知到另外一個長時間運行的計算任務,這兩個計算任務都完成後,將計算的結果與另外一個查詢操做結果合併」。可是,使用Future中提供的方法完成這樣的操做又是另一回事。這也是咱們須要更具描述能力的特性的緣由,好比下面這些。
一個很是有用,不過不那麼精確的格言這麼說:「Completable-Future對於Future的意義就像Stream之於Collection。」讓咱們比較一下這兩者。
Java的API提供了不少有用的組件,能幫助你構建複雜的應用。不過,Java API也不老是完美的。咱們相信大多數有經驗的程序員都會贊同Java 8以前的庫對日期和時間的支持就很是不理想。然而,你也不用太擔憂:Java 8中引入全新的日期和時間API就是要解決這一問題。
開始使用新的日期和時間API時,你最早碰到的多是LocalDate類。該類的實例是一個不可變對象,它只提供了簡單的日期,並不含當天的時間信息。另外,它也不附帶任何與時區相關的信息。
你能夠經過靜態工廠方法of建立一個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) //你還可使用工廠方法從系統時鐘中獲取當前的日期: LocalDate today = LocalDate.now();
LocalTime和LocalDateTime都提供了相似的方法。
做爲人,咱們習慣於以星期幾、幾號、幾點、幾分這樣的方式理解日期和時間。毫無疑問,這種方式對於計算機而言並不容易理解。從計算機的角度來看,建模時間最天然的格式是表示一個持續時間段上某個點的單一大整型數。這也是新的java.time.Instant類對時間建模的方式,基本上它是以Unix元年時間(傳統的設定爲UTC時區1970年1月1日午夜時分)開始所經歷的秒數進行計算。
你能夠經過向靜態工廠方法ofEpochSecond傳遞一個表明秒數的值建立一個該類的實例。靜態工廠方法ofEpochSecond還有一個加強的重載版本,它接收第二個以納秒爲單位的參數值,對傳入做爲秒數的參數進行調整。重載的版本會調整納秒參數,確保保存的納秒分片在0到999 999 999之間。這意味着下面這些對ofEpochSecond工廠方法的調用會返回幾乎一樣的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秒)
正如你已經在LocalDate及其餘爲便於閱讀而設計的日期-時間類中所看到的那樣,Instant類也支持靜態工廠方法now,它可以幫你獲取當前時刻的時間戳。咱們想要特別強調一點,Instant的設計初衷是爲了便於機器使用。它包含的是由秒及納秒所構成的數字。因此,它沒法處理那些咱們很是容易理解的時間單位。好比下面這段語句:
int day = Instant.now().get(ChronoField.DAY_OF_MONTH);
它會拋出下面這樣的異常:
java.time.temporal.UnsupportedTemporalTypeException: Unsupported field:
DayOfMonth
可是你能夠經過Duration和Period類使用Instant,接下來咱們會對這部份內容進行介紹。
目前爲止,你看到的全部類都實現了Temporal接口,Temporal接口定義瞭如何讀取和操縱爲時間建模的對象的值。以前的介紹中,咱們已經瞭解了建立Temporal實例的幾種方法。很天然地你會想到,咱們須要建立兩個Temporal對象之間的duration。Duration類的靜態工廠方法between就是爲這個目的而設計的。你能夠建立兩個LocalTimes對象、兩個LocalDateTimes對象,或者兩個Instant對象之間的duration,以下所示:
Duration d1 = Duration.between(time1, time2); Duration d1 = Duration.between(dateTime1, dateTime2); Duration d2 = Duration.between(instant1, instant2);
因爲LocalDateTime和Instant是爲不一樣的目的而設計的,一個是爲了便於人閱讀使用,另外一個是爲了便於機器處理,因此你不能將兩者混用。若是你試圖在這兩類對象之間建立duration,會觸發一個DateTimeException異常。此外,因爲Duration類主要用於以秒和納秒衡量時間的長短,你不能僅向between方法傳遞一個LocalDate對象作參數。
若是你須要以年、月或者日的方式對多個時間單位建模,可使用Period類。使用該類的工廠方法between,你可使用獲得兩個LocalDate之間的時長,以下所示:
Period tenDays = Period.between(LocalDate.of(2014, 3, 8), LocalDate.of(2014, 3, 18));
最後,Duration和Period類都提供了不少很是方便的工廠類,直接建立對應的實例;換句話說,就像下面這段代碼那樣,再也不是隻能以兩個temporal對象的差值的方式來定義它們的對象。
建立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類共享了不少類似的方法:
方法名 | 是不是靜態方法 | 方法描述 |
---|---|---|
between | 是 | 建立兩個時間點之間的interval |
from | 是 | 由一個臨時時間點建立interval |
of | 是 | 由它的組成部分建立interval的實例 |
parse | 是 | 由字符串建立interval的實例 |
addTo | 否 | 建立該interval的副本,並將其疊加到某個指定的temporal對象 |
get | 否 | 讀取該interval的狀態 |
isNegative | 否 | 檢查該interval是否爲負值,不包含零 |
isZero | 否 | 檢查該interval的時長是否爲零 |
minus | 否 | 經過減去必定的時間建立該interval的副本 |
multipliedBy | 否 | 將interval的值乘以某個標量建立該interval的副本 |
negated | 否 | 以忽略某個時長的方式建立該interval的副本 |
plus | 否 | 以增長某個指定的時長的方式建立該interval的副本 |
subtractFrom | 否 | 從指定的temporal對象中減去該interval |