摘要:可以使用方法替換常規循環以上代碼的產出所有這些原始流都像常規對象流一樣工作,但有以下不同之處原始流使用專門的表達式,例如代替或代替。原始流支持額外的終端聚合操作,以上代碼的產出有時將常規對象流轉換為基本流是有用的,反之亦然。
本文提供了有關Java 8 Stream的深入概述。當我第一次讀到的Stream API,我感到很困惑,因為它聽起來類似Java I/O的InputStream,OutputStream。但Java 8 Stream是完全不同的東西。Streams是Monads,因此在為Java提供函數式編程方面發揮了重要作用:
在函數式編程中,monad是表示定義為步驟序列的計算的結構。具有monad結構的類型定義鏈操作的含義,或將該類型的函數嵌套在一起。
本文詳解如何使用Java 8 Stream以及如何使用不同類型的可用流操作。您將了解處理順序以及流操作的順序如何影響運行時性能。并對更強大的reduce,collect,flatMap流操作詳細介紹。
如果您還不熟悉Java 8 lambda表達式,函數接口和方法引用,那么您可能需要了解Java 8。
Stram如何工作Stream表示一系列元素,并支持不同類型的操作以對這些元素執行計算:
Liststreams = Arrays.asList("a1", "a2", "b1", "c2", "c1"); streams .stream() .filter(s -> s.startsWith("c")) .map(String::toUpperCase) .sorted() .forEach(System.out::println);
以上代碼的產出:
C1 C2
Stream操作是中間操作或終端操作。中間操作返回一個流,因此我們可以鏈接多個中間操作而不使用分號。終端操作無效或返回非流結果。在上述例子中filter,map和sorted是中間操作,而forEach是一個終端的操作。有關所有可用流操作的完整列表,請參閱Stream Javadoc。如上例中所見的這種流操作鏈也稱為操作管道。
大多數流操作都接受某種lambda表達式參數,這是一個指定操作的確切行為的功能接口。大多數這些操作必須是不受干擾和無狀態。
當函數不修改流的基礎數據源時,該函數是不受干擾的,例如在上面的示例中,沒有lambda表達式通過從集合中添加或刪除元素來修改streams。
當操作的執行是確定性的時,函數是無狀態的,例如在上面的示例中,沒有lambda表達式依賴于任何可變變量或來自外部作用域的狀態,其可能在執行期間改變。
不同種類的Stream可以從各種數據源創建流,尤其是集合。Lists和Sets支持新的方法stream()和parallelStream()來創建順序流或并行流。并行流能夠在多個線程上操作,后面的部分將對此進行介紹。我們現在關注的是順序流:
Arrays.asList("a1", "a2", "a3") .stream() .findFirst() .ifPresent(System.out::println);
以上代碼的產出:
a1
在對象列表上調用stream()方法將返回常規對象流。但是我們不必創建集合以便使用流,就像我們在下一個代碼示例中看到的那樣:
Stream.of("a1", "a2", "a3") .findFirst() .ifPresent(System.out::println);
以上代碼的產出:
a1
只是用來Stream.of()從一堆對象引用創建一個流。
除了常規對象流之外,Java 8還附帶了特殊類型的流,用于處理原始數據類型int,long以及double。你可能已經猜到了IntStream,LongStream,DoubleStream。
IntStreams可以使用IntStream.range()方法替換常規for循環:
IntStream.range(1, 4) .forEach(System.out::println);
以上代碼的產出:
1 2 3
所有這些原始流都像常規對象流一樣工作,但有以下不同之處:原始流使用專門的lambda表達式,例如IntFunction代替Function或IntPredicate代替Predicate。原始流支持額外的終端聚合操作,sum(),average():
Arrays.stream(new int[] {1, 2, 3}) .map(n -> 2 * n + 1) .average() .ifPresent(System.out::println);
以上代碼的產出:
5.0
有時將常規對象流轉換為基本流是有用的,反之亦然。為此,對象流支持特殊的映射操作mapToInt(),mapToLong(),mapToDouble:
Stream.of("a1", "a2", "a3") .map(s -> s.substring(1)) .mapToInt(Integer::parseInt) .max() .ifPresent(System.out::println);
以上代碼的產出:
3
可以通過mapToObj()方式將原始流轉換為對象流:
IntStream.range(1, 4) .mapToObj(i -> "a" + i) .forEach(System.out::println);
以上代碼的產出:
a1 a2 a3
下面是一個組合示例:雙精度流首先映射到int流,然后映射到字符串的對象流:
Stream.of(1.0, 2.0, 3.0) .mapToInt(Double::intValue) .mapToObj(i -> "a" + i) .forEach(System.out::println);
以上代碼的產出:
a1 a2 a3處理過程
現在我們已經學會了如何創建和使用不同類型的流,讓我們深入了解如何在流程下處理流操作。
中間操作的一個重要特征是懶惰。查看缺少終端操作的示例:
Stream.of("d2", "a2", "b1", "b3", "c") .filter(s -> { System.out.println("filter: " + s); return true; });
執行此代碼段時,不會向控制臺打印任何內容。這是因為只有在存在終端操作時才執行中間操作。
讓我們通過forEach終端操作擴展上面的例子:
Stream.of("d2", "a2", "b1", "b3", "c") .filter(s -> { System.out.println("filter: " + s); return true; }) .forEach(s -> System.out.println("forEach: " + s));
執行此代碼段會在控制臺上產生所需的輸出:
filter: d2 forEach: d2 filter: a2 forEach: a2 filter: b1 forEach: b1 filter: b3 forEach: b3 filter: c forEach: c
結果的順序可能會令人驚訝。默認認為是在流的所有元素上一個接一個地水平執行操作。但相反,每個元素都沿著鏈垂直移動。第一個字符串“d2”通過filter,然后forEach,然后處理第二個字符串“a2”。
此行為可以減少對每個元素執行的實際操作數,如下一個示例所示:
Stream.of("d2", "a2", "b1", "b3", "c") .map(s -> { System.out.println("map: " + s); return s.toUpperCase(); }) .anyMatch(s -> { System.out.println("anyMatch: " + s); return s.startsWith("A"); });
代碼產出
map: d2 anyMatch: D2 map: a2 anyMatch: A2
一旦謂詞應用于給定的輸入元素,anyMatch操作將返回true。這對于傳遞給“A2”的第二個元素是正確的。由于流鏈的垂直執行,map在這種情況下映射只需執行兩次。因此,不是映射流的所有元素,而是map盡可能少地調用。
復雜的處理過程下一個示例包括兩個map,filter中間操作和forEach終端操作。讓我們再次檢查這些操作是如何執行的:
Stream.of("d2", "a2", "b1", "b3", "c") .map(s -> { System.out.println("map: " + s); return s.toUpperCase(); }) .filter(s -> { System.out.println("filter: " + s); return s.startsWith("A"); }) .forEach(s -> System.out.println("forEach: " + s));
代碼產出:
map: d2 filter: D2 map: a2 filter: A2 forEach: A2 map: b1 filter: B1 map: b3 filter: B3 map: c filter: C
正如您可能已經猜到的,對于底層集合中的每個字符串,map和filter都被調用5次,而forEach只被調用一次。
如果我們改變操作的順序,移動filter到鏈的開頭,我們可以大大減少實際的執行次數:
Stream.of("d2", "a2", "b1", "b3", "c") .filter(s -> { System.out.println("filter: " + s); return s.startsWith("a"); }) .map(s -> { System.out.println("map: " + s); return s.toUpperCase(); }) .forEach(s -> System.out.println("forEach: " + s));
代碼產出:
filter: d2 filter: a2 map: a2 forEach: A2 filter: b1 filter: b3 filter: c
現在,map只調用一次,因此操作管道對大量輸入元素的執行速度要快得多。在編寫復雜的方法鏈時要記住這一點。
讓我們通過一個sorted額外的操作來擴展上面的例子:
Stream.of("d2", "a2", "b1", "b3", "c") .sorted((s1, s2) -> { System.out.printf("sort: %s; %s ", s1, s2); return s1.compareTo(s2); }) .filter(s -> { System.out.println("filter: " + s); return s.startsWith("a"); }) .map(s -> { System.out.println("map: " + s); return s.toUpperCase(); }) .forEach(s -> System.out.println("forEach: " + s));
排序是一種特殊的中間操作。這是一個所謂的有狀態操作,因為為了對在排序期間必須維護狀態的元素集合進行排序。
執行此示例將導致以下控制臺輸出:
sort: a2; d2 sort: b1; a2 sort: b1; d2 sort: b1; a2 sort: b3; b1 sort: b3; d2 sort: c; b3 sort: c; d2 filter: a2 map: a2 forEach: A2 filter: b1 filter: b3 filter: c filter: d2
首先,對整個輸入集合執行排序操作。換句話說,sorted是水平執行的。因此,在這種情況下sorted,對輸入集合中的每個元素的多個組合調用八次。
我們可以通過重新排序鏈來優化性能:
Stream.of("d2", "a2", "b1", "b3", "c") .filter(s -> { System.out.println("filter: " + s); return s.startsWith("a"); }) .sorted((s1, s2) -> { System.out.printf("sort: %s; %s ", s1, s2); return s1.compareTo(s2); }) .map(s -> { System.out.println("map: " + s); return s.toUpperCase(); }) .forEach(s -> System.out.println("forEach: " + s));
代碼產出
filter: d2 filter: a2 filter: b1 filter: b3 filter: c map: a2 forEach: A2
在此示例sorted從未被調用過,因為filter將輸入集合減少到只有一個元素。因此,對于較大的輸入集合,性能會大大提高。
重用StreamJava 8 Stream無法重用。只要您調用任何終端操作,流就會關閉:
Streamstream = Stream.of("d2", "a2", "b1", "b3", "c") .filter(s -> s.startsWith("a")); stream.anyMatch(s -> true); // ok stream.noneMatch(s -> true); // exception
在同一流上的anyMatch之后調用noneMatch會導致以下異常:
java.lang.IllegalStateException: stream has already been operated upon or closed at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:229) at java.util.stream.ReferencePipeline.noneMatch(ReferencePipeline.java:459) at com.winterbe.java8.Streams5.test7(Streams5.java:38) at com.winterbe.java8.Streams5.main(Streams5.java:28)
為了克服這個限制,我們必須為我們想要執行的每個終端操作創建一個新的流鏈,例如我們可以創建一個流供應商來構建一個新的流,其中已經設置了所有中間操作:
Supplier> streamSupplier = () -> Stream.of("d2", "a2", "b1", "b3", "c") .filter(s -> s.startsWith("a")); streamSupplier.get().anyMatch(s -> true); // ok streamSupplier.get().noneMatch(s -> true); // ok
每次調用get()構造一個我們保存的新流,以調用所需的終端操作。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/72892.html
摘要:接受包含四種不同操作的操作供應商,累加器,組合器和修整器。累加器用于將每個人的大寫名稱添加到。第二種方法接受標識值和累加器。由于累加器是并行調用的,因此需要組合器來對各個累加值求和。 Streams支持大量不同的操作。我們已經了解了最重要的操作,如filter,map。發現所有其他可用的操作(參見Stream Javadoc)。我們深入研究更復雜的操作collect,flatMap,r...
摘要:上一篇我們介紹了的概念與實際的一些操作,本篇我們繼續來學習的另一個重要操作,分組與分區。注意到分組后的返回類型是,結果集中會將作為,對應的集合作為返回。 上一篇我們介紹了Strem的概念與實際的一些操作,本篇我們繼續來學習Stream的另一個重要操作,分組與分區。我們在上一篇介紹Stream的操作時,會經常使用到Collectors這個類,這個類實際上是一個封裝了很多常用的匯聚操作的一...
摘要:本文基于環境,采用為基礎來構建實時人臉檢測與識別系統,探索人臉識別系統在現實應用中的難點。對于人臉檢測方法,效果好于的方法,但是檢測力度也難以達到現場應用標準。本文中,我們采用了基于深度學習方法的人臉檢測系統。 git地址:https://github.com/chenlinzho... 本文主要介紹了系統涉及的人臉檢測與識別的詳細方法,該系統基于python2.7.10/opencv...
摘要:本文基于環境,采用為基礎來構建實時人臉檢測與識別系統,探索人臉識別系統在現實應用中的難點。對于人臉檢測方法,效果好于的方法,但是檢測力度也難以達到現場應用標準。本文中,我們采用了基于深度學習方法的人臉檢測系統。 git地址:https://github.com/chenlinzho... 本文主要介紹了系統涉及的人臉檢測與識別的詳細方法,該系統基于python2.7.10/opencv...
摘要:爬蟲下載一最近在學習的爬蟲,并且玩的不亦說乎,因此寫個博客,記錄并分享一下。 Python3爬蟲下載pdf(一) 最近在學習python的爬蟲,并且玩的不亦說乎,因此寫個博客,記錄并分享一下。 需下載以下模塊 bs4 模塊 requests 模塊 一、源碼 功能:下載指定url內的所有的pdf 語法:將含有pdf的url放到腳本后面執行就可以了 from bs4 import...
閱讀 2400·2021-09-08 09:45
閱讀 3340·2021-09-08 09:45
閱讀 3097·2019-08-30 15:54
閱讀 3348·2019-08-26 13:54
閱讀 1405·2019-08-26 13:26
閱讀 1384·2019-08-26 13:23
閱讀 909·2019-08-23 17:57
閱讀 2178·2019-08-23 17:14