【java8新特性】——Stream API详解(二)
一、簡介
java8新添加了一個特性:流Stream。Stream讓開發者能夠以一種聲明的方式處理數據源(集合、數組等),它專注于對數據源進行各種高效的聚合操作(aggregate operation)和大批量數據操作 (bulk data operation)。
Stream API將處理的數據源看做一種Stream(流),Stream(流)在Pipeline(管道)中傳輸和運算,支持的運算包含篩選、排序、聚合等,當到達終點后便得到最終的處理結果。
幾個關鍵概念:
Stream API的特點:
二、簡單示例
我們來看一個簡單的示例,統計整數數組中正數的個數:
1.在java8之前:
public static void main(String[] args){ List<Integer> numbers = Arrays.asList(-1, -2, 0, 4, 5);long count = 0;for(Integer number: numbers){if(number > 0){count++;}}System.out.println("Positive count: " + count);}2.在java8之后:
public static void main(String[] args){ List<Integer> numbers = Arrays.asList(-1, -2, 0, 4, 5);long count = numbers.parallelStream().filter(i -> i>0).count();System.out.println("Positive count: " + count);}可以看到,上例中,使用filter()方法對數組進行了過濾,使用count()方法對過濾后的數組進行了大小統計,且使parallelStream()方法為集合創建了并行流,自動采用并行運算提高速度。在更復雜的場景,還可以用forEach()、map()、limit()、sorted()、collect()等方法進行進一步的流運算。
三、典型接口詳解
本節以典型場景為例,列出Stream API常用接口的用法,并附上相應代碼。
需要說明的是,Stream API中存在很多方法重載,同名方法本文中可能僅列舉一個,請讀者注意~
3.1 Stream的生成
java8 Stream API支持串行或并行的方式,可以簡單看下jdk1.8 Collection接口的源碼(注釋只截取部分):
/*** @return a sequential {@code Stream} over the elements in this collection* @since 1.8*/default Stream<E> stream() {return StreamSupport.stream(spliterator(), false);}/*** @return a possibly parallel {@code Stream} over the elements in this collection* @since 1.8*/default Stream<E> parallelStream() {return StreamSupport.stream(spliterator(), true);}可以看出,在集合類的接口(Collection)中,分別用兩種方式來生成:
應該注意的是,使用parallelStream()生成并行流后,對集合元素的遍歷是無序的。
3.2 forEach()方法
簡單看下forEach()方法的源碼(注釋只截取部分):
/*** Performs an action for each element of this stream.*/void forEach(Consumer<? super T> action);forEach()方法的參數為一個Consumer(消費函數,一個函數式接口)對象,forEach()方法用來迭代流中的每一個數據,例如:
public static void main(String[] args){ List<Integer> numbers = Arrays.asList(-1, -2, 0, 4, 5);numbers.stream().forEach(n -> System.out.println("List element: " + n));}上例中,對數組的每個元素進行串行遍歷,并打印每個元素的值。
ps:
集合的頂層接口Iterable中也投forEach方法,可以直接對數組元素進行遍歷:
public static void main(String[] args){ List<Integer> numbers = Arrays.asList(-1, -2, 0, 4, 5);numbers.forEach(n -> System.out.println("List element: " + n));}當然用Strem API的好處不僅僅是遍歷~~~
3.3 map()方法
簡單看下map()方法的源碼(注釋只截取部分):
/*** Returns a stream consisting of the results of applying the given function to the elements of this stream.* @param <R> The element type of the new stream* @param mapper a <a href="package-summary.html#NonInterference">non-interfering</a>,* <a href="package-summary.html#Statelessness">stateless</a>* function to apply to each element* @return the new stream*/<R> Stream<R> map(Function<? super T, ? extends R> mapper);map()方法的參數為Function(函數式接口)對象,map()方法將流中的所有元素用Function對象進行運算,生成新的流對象(流的元素類型可能改變)。舉例如下:
public static void main(String[] args){ List<Integer> numbers = Arrays.asList(-1, -2, 0, 4, 5);numbers.stream().map( n -> Math.abs(n)).forEach(n -> System.out.println("Element abs: " + n));}上例中,用map()方法計算了所有數組元素的絕對值并生成了一個新的流,然后再用forEach()遍歷打印。
3.4 flatMap()方法
簡單看下flatMap()方法的源碼(省略注釋):
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);顯然,跟map()方法不同的是,Function函數的返回值類型是Stream<? extends R>類型,而不是R類型,即Function函數返回一個Stream流,這樣flatMap()能夠將一個二維的集合映射成一個一維的集合,比map()方法擁有更高的映射深度(此處可能有一點繞,可結合例子理解),作個簡單示例如下:
有一個字符串數組:
List<String> list = Arrays.asList("1 2", "3 4", "5 6");其有三個元素,每個元素有兩個數組并用空格隔開,如果每個元素以空格分割成2個元素,并遍歷打印這6個元素,
用flatMap()方法如下:
list.stream().flatMap(item -> Arrays.stream(item.split(" "))).forEach(System.out::println);而用map()方法:
list.stream().map(item -> Arrays.stream(item.split(" "))).forEach(n ->n.forEach(System.out::println));可見,用map()方法,返回了一個“流中流”,需要在每個Stream元素遍歷時,再加一層forEach進行遍歷。
3.5 filter()方法
簡單看下filter()方法的源碼(注釋只截取部分): /*** Returns a stream consisting of the elements of this stream that match the given predicate.** <p>This is an <a href="package-summary.html#StreamOps">intermediate operation</a>.** @param predicate a <a href="package-summary.html#NonInterference">non-interfering</a>,* <a href="package-summary.html#Statelessness">stateless</a>* predicate to apply to each element to determine if it should be included* @return the new stream*/Stream<T> filter(Predicate<? super T> predicate);filter()方法的參數為Predicate(函數式接口)對象,再lambda表達式的講解中我們提到過這個接口,一般用它進行過濾。正如第二章中示例:
public static void main(String[] args){ List<Integer> numbers = Arrays.asList(-1, -2, 0, 4, 5);long count = numbers.parallelStream().filter(i -> i>0).count();System.out.println("Positive count: " + count);}用filter方法很容易過濾出整數數組中的自然數。
3.6 reduce()方法
reduce操作又稱為折疊操作,用于將流中的所有值合成一個。reduce()方法的源碼(不提供計算初始值的reduce方法)(省略注釋):
Optional<T> reduce(BinaryOperator<T> accumulator);reduce()方法參數為BinaryOperator類型的累加器(它接受兩個類型相同的參數,返回值類型跟參數類型相同),返回一個Optional對象。
實際上,Stream API中的mapToInt()方法返回的IntStream接口有類似的 average()、count()、sum()等方法就是做reduce操作,類似的還有mapToLong()、mapToDouble() 方法。當然,我們也可以用reduce()方法來自定義reduce操作。例如我們用reduce()方法來進行整數數組求和操作:
上例中利用reduce()方法結合lambda表達式輕易的實現了數組的求和功能。
3.7 collect()方法
簡單看下collect()方法的源碼(注釋只截取部分):
/*** @param <R> the type of the result* @param <A> the intermediate accumulation type of the {@code Collector}* @param collector the {@code Collector} describing the reduction* @return the result of the reduction*/<R, A> R collect(Collector<? super T, A, R> collector);collect()方法的參數為一個java.util.stream.Collector類型對象,可以用java.util.stream.Collectors工具類提供的靜態方法來生成,Collectors類實現很多的歸約操作,如Collectors.toList()、Collectors.toSet()、Collectors.joining()(joining適用于字符串流)等。看一個簡單示例:
public static void main(String[] args){ List<Integer> numbers = Arrays.asList(-1, -2, 0, 4, 5);List<Integer> abss = numbers.stream().map( n -> Math.abs(n)).collect(Collectors.toList());System.out.println("Abs list: " + abss);}上例中,用map()方法生成新的流,再用collect()方法返回原數組的絕對值數組。
3.8 summaryStatistics()方法進行數值統計
其實summaryStatistics()方法并不是Stream接口的方法,而是Stream API采用mapToInt()、mapToLong()、mapToDouble()三個方法分別生成IntStream 、LongStream 、DoubleStream 三個接口類型的對象,這個方法的參數分別為3個函數式接口ToIntFunction、ToLongFunction、ToDoubleFunction,使用時可以用lambda表達式計算返回對應的int、long、double類型即可,簡單看下這三個方法的源碼(省略注釋):
IntStream mapToInt(ToIntFunction<? super T> mapper);LongStream mapToLong(ToLongFunction<? super T> mapper);DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper);IntStream 、LongStream 、DoubleStream 三個接口類型都有一個summaryStatistics()方法,其中,
在IntSummaryStatistics、LongSummaryStatistics 、DoubleSummaryStatistics 三個接口類型(位于java.util包下)中,都有諸如統計數量、最大值、最小值、求和、平均值等方法(方法名和返回類型可能不同),利用這些方法我們可以方便的進行數值統計。以IntSummaryStatistics工具包 為例:
public static void main(String[] args){List<Integer> numbers = Arrays.asList(-1, -2, 0, 4, 5);IntSummaryStatistics stats = numbers.stream().mapToInt((x) -> x).summaryStatistics();System.out.println("Max : " + stats.getMax());System.out.println("Min : " + stats.getMin());System.out.println("Sum : " + stats.getSum());System.out.println("Average : " + stats.getAverage());System.out.println("Count : " + stats.getCount());}3.9 其它方法
Stream API還有一些其它的方法,比如:
limit() 獲取指定數量的流
sorted() 對流進行排序
distinct() 去重
skip() 跳過指定數量的元素
peek() 生成一個包含原Stream的所有元素的新Stream,并指定消費函數
count() 計算元素數量
…
感興趣的讀者可以閱讀源碼,讀到這里已經很容易理解了,本文不再贅述。
四、注意事項
Stream中的操作從概念上講分為中間操作和終端操作:
- 中間操作:例如peek()方法提供Consumer(消費)函數,但執行peek()方法時不會執行Consumer函數,而是等到流真正被消費時(終端操作時才進行消費)才會執行,這種操作為中間操作;
- 終端操作:例如forEach()、collect()、count()等方法會對流中的元素進行消費,并執行指定的消費函數(peek方法提供的消費函數在此時執行),這種操作為終端操作。
要理解中間操作和終端操作的概念,防止埋坑~
-
【java8新特性】——lambda表達式與函數式接口詳解(一)
-
【java8新特性】——Stream API詳解(二)
-
【java8新特性】——Optional詳解(三)
-
【java8新特性】——方法引用(四)
-
【java8新特性】——默認方法(五)
總結
以上是生活随笔為你收集整理的【java8新特性】——Stream API详解(二)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java switch小程序,小程序自定
- 下一篇: 湖南工大11级C语言网上作业,湖南工大1