日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

Java8————Stream API

發布時間:2025/3/12 java 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java8————Stream API 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

引言

Java8 加入了java.util.stream包,這個包中的相關API將極大的增強容器對象對元素的操作能力。

它專注于對集合對象進行各種便利、高效的聚合操作,或大批量數據處理。

Stream API借助于同樣新出現的Lambda表達式,極大的提高了編程效率和程序信噪比。

它提供了串行和并行兩種模式進行匯聚操作。并行模式底層采用 Fork / Join 框架來拆分任務和加速處理過程。

什么是流?

一、流的概念

1、流不是數據結構

它沒有內部存儲,它只是用操作管道從source(數據結構、數組、IO channel)抓取數據。

2、不修改源數據

例如Stream 的 filter操作會產生一個不包含被過濾元素的新的Stream,而不是從source中刪除那些元素。

3、流的操作參數

所有的Stream操作必須以Lambda表達式作為參數。

4、不支持索引訪問

Stream操作實際上是?增強For循環 的函數編程變式,它沒有元素下標的訪問方式。

5、流可以轉換成數組或者List

6、惰性化

Intermediate操作永遠是惰性化的

7、并行能力

當一個集合不要求元素的順序時,我們可以通過Stream的并行化特性來充分利用多核資源,不需要再寫多線程代碼,所有對它的操作會自動并行進行。

8、可以是無限的

集合有固定大小,Stream則不必,limit(n)、findFirst()這類short-circuiting操作可以對無限的Stream進行運算并很快完成。

二、流的操作分類

流的操作類型被分為三種:Intermediate、Terminal、short-circuiting

Intermediate :代表流的中間操作,這種操作的目的主要是打開流,做出某種程度的映射或過濾,然后返回一個新的流,交給下一個操作使用。這類操作是惰性的,也就是說,僅僅調用到這類方法,并沒有真正開始流的遍歷。

Terminal :一個流只能有一個Terminal操作。所以這必定是流的最后一個操作。而Terminal操作的執行,才會真正開始流的遍歷,并且會生成一個結果,或者一個副作用。

short-circuiting?:對于Intermediate 操作,如果接收的是一個無限大的Stream,則返回一個有限的新Stream;對于Terminal 操作,如果它接收的是一個無限大的Stream,但能在有限的時間計算出結果。

三、惰性化(lazy)

我們說Intermediate操作都是惰性化的,這如何理解?在對于一個Stream進行多次轉換操作(Intermediate操作),每次操作都對Stream中的每個元素進行轉換,而且是執行多次,這樣時間復雜度就是 N(轉換次數)個for循環里所有操作都執行完的總和嗎?其實不是這樣的!

我們說轉換操作是Lazy的,多個轉換操作只會在Terminal 操作的時候融合進來,一次循環完成。

我們可以這樣簡單的類比,在Java 8 未引進Stream API的時候,使用命令式進行for循環,并對每個元素進行諸如 if-else 、賦值、計算、獲取、添加等操作,而這些操作你可以理解為Stream中的Intermediate操作,只有在for循環真正執行的時候才會執行它們,這就是惰性化的語義,即提前安排好篩選、計算等Intermediate操作,當循環時再執行它們

常見用法歸納

一、創建流

我們可以通過一個“集合”對象來創建流,這個集合對象并不限于Collection接口,還包含那些能容納多個對象的容器。

創建流的方式大致可以分為三種:Arrays.stream()、Stream.of()、集合.stream(),下面舉例來說明:

1、數組生成流

// 基本類型數組 int[] numx = new int[] { 1, 2, 2, 3, 5 }; // 方法一: IntStream stream1 = Arrays.stream(numx);// 方法二: IntStream stream2 = IntStream.of(numx);// 引用類型數組 Integer[] nums = new Integer[] { 1, 2, 2, 3, 5 };// 方法一: Stream<Integer> stream3 = Arrays.stream(nums);// 方法二: Stream<Integer> stream4 = Stream.of(nums);

需要注意的是,對于基本數值型,目前有三種對應的包裝類型 Stream:

IntStream、LongStream、DoubleStream。當然我們也可以用 Stream<Integer>、Stream<Long> >、Stream<Double>,但是 boxing 和 unboxing 會很耗時,所以特別為這三種基本數值型提供了對應的 Stream。?Java 8 中還沒有提供其它數值型 Stream,因為這將導致擴增的內容較多。而常規的數值型聚合運算可以通過上面三種 Stream 進行。

?2、集合生成流

List<Integer> numsList = Arrays.asList( 1, 2, 2, 3, 5 ); // 使用parallelStream會將List進行分段并行處理,因此處理的順序是不固定的。 Stream<Integer> parallelStream = numsList.parallelStream();

二、流轉化為容器(Terminal)

Stream<String> names = Arrays.asList("Tom", "Jerry", "Tim", "Morty").stream();

1、Stream轉Array

String[] namesArr = names.toArray(String[]::new);

2、?Stream轉Collection

List<String> list1 = names.collect(Collectors.toList()); // 或 List<String> list2 = names.collect(Collectors.toCollection(ArrayList::new)); Set<String> set = names.collect(Collectors.toSet()); Stack<String> stack = names.collect(Collectors.toCollection(Stack::new));

3、Stream轉String

String str = names.collect(Collectors.joining());// joining()有重載

?三、映射操作(Intermediate)

map將input stream中的每一個元素,映射成output? stream中的另外一個元素(一對一映射)

List<String> output = names.map(String::toUpperCase).collect(Collectors.toList());

四、多集合映射操作(Intermediate)

flatMap(功能和map相同,只不過映射是一對多),flatMap 把 input Stream 中的層級結構扁平化,就是將最底層元素抽出來放到一起。

List<String> names1 = Arrays.asList("Tom", "Jerry", "Tim", "Morty"); List<String> names2 = Arrays.asList("Tony", "Jack", "Tina", "Marry"); List<String> collect = Stream.of(names1, names2).flatMap(ns -> ns.stream().map(String::toLowerCase)).collect(Collectors.toList());

五、篩選操作(Intermediate)

filter對原始 Stream 進行某項測試,符合條件表達式的元素被留下來生成一個新 Stream 或集合。

Integer[] sixNums = { 1, 2, 3, 4, 5, 6 }; Integer[] evens = Stream.of(sixNums).filter(n -> n % 2 == 0).toArray(Integer[]::new);

六、循環操作(Terminal)

forEach() 方法接收一個 Lambda 表達式,然后在 Stream 的每一個元素上執行該表達式。

但一般認為,forEach 和常規 for 循環的差異不涉及到性能,它們僅僅是函數式風格與傳統 Java 風格的差別.

注意:forEach 不能修改自己包含的本地變量值,也不能用 break/return 之類的關鍵字提前結束循環。

當需要為多核系統優化時,可以 parallelStream().forEach()。另外一點需要注意,forEach 是 terminal 操作。具有相似功能的 intermediate 操作 peek 可以達到上述目的。

Stream.of("one", "two", "three", "four").filter(e -> e.length() > 3).peek(e -> System.out.println("Filtered value: " + e)).map(String::toUpperCase).peek(e -> System.out.println("Mapped value: " + e)).collect(Collectors.toList());

七、第一個元素(Terminal)

findFirst是一個 termimal 兼 short-circuiting 操作,它總是返回 Stream 的第一個元素,或者空。

注意,它的返回值類型:Optional。使用Optional的目的是盡可能避免 NullPointerException。它提供的是編譯時檢查,能極大的降低 NPE 這種 Runtime Exception 對程序的影響。

Optional<String> firstName = names2.stream().findFirst();

八、聚合操作(Terminal)

reduce方法的主要作用是把 Stream 元素組合起來。它提供一個起始值(種子),然后依照運算規則(BinaryOperator),和前面 Stream 的第一個、第二個、第 n 個元素組合。從這個意義上說,字符串拼接、數值的 sum、min、max、average 都是特殊的 reduce。

下面代碼例如第一個示例的 reduce(),第一個參數(空白字符)即為起始值,第二個參數(String::concat)為 BinaryOperator。這類有起始值的 reduce() 都返回具體的對象。而對于第四個示例沒有起始值的 reduce(),由于可能沒有足夠的元素,返回的是 Optional,請留意這個區別。

// 字符串連接,concat = "ABCD" String concat = Stream.of("A", "B", "C", "D").reduce("", String::concat); // 求最小值,minValue = -3.0 double minValue = Stream.of(-1.5, 1.0, -3.0, -2.0).reduce(Double.MAX_VALUE, Double::min); // 求和,sumValue = 10, 有起始值 int sumValue = Stream.of(1, 2, 3, 4).reduce(0, Integer::sum); // 求和,sumValue = 10, 無起始值 sumValue = Stream.of(1, 2, 3, 4).reduce(Integer::sum).get(); // 過濾,字符串連接,concat = "ace" String s = Stream.of("a", "B", "c", "D", "e", "F").filter(x -> x.compareTo("Z") > 0).reduce("", String::concat);

九、limit/skip (Short-circuiting)

limit 返回 Stream 的前面 n 個元素;skip 則是扔掉前 n 個元素(它是由一個叫 subStream 的方法改名而來)。

List<String> persons = new ArrayList<>(); for (int i = 1; i <= 10000; i++) {persons.add(new String("name" + i)); } List<String> personNameList = persons.stream().map(String::toUpperCase).limit(10).skip(3).collect(Collectors.toList());

?上述代碼是一個有 10000 個元素的 Stream,但在 short-circuiting 操作 limit 和 skip 的作用下,管道中 map 操作指定的toUpperCase()方法的執行次數為 limit 所限定的 10 次,而最終返回結果再跳過前 3 個元素后只有后面 7 個返回。

執行結果:

注意,有一種情況 limit/skip 無法達到 short-circuiting 目的,就是把它們放在 Stream 的排序操作后,原因跟 sorted這個 intermediate 操作有關:此時系統并不知道 Stream 排序后的次序如何,所以 sorted 中的操作看上去就像完全沒有被 limit 或者 skip 一樣。

經典案例:內存全量數據實現分頁返回:

如下代碼所示,page 從 1 開始,分別翻頁查詢,可以得到完美的分頁輸出結果。

其中skip代表一個偏移量,limit代表輸出限制,由此方式達到了 SQL 語句中 limit x, y 的效果。

public class TestMemoryPage {public static final List<String> totalNameList = new ArrayList<>();static {for (int i = 1; i <= 100; i++) {totalNameList.add("name"+ i);}}public static void main(String[] args) {// 可自由調整分頁大小int pageSize = 9;int total = totalNameList.size();int totalPage = (total + pageSize - 1) / pageSize;System.out.println(totalNameList);System.out.println("pageSize = "+pageSize+", total = "+total+", totalPage = "+totalPage);// 分頁開始for (int page = 1; page <= totalPage; page++) {List<String> pageList = totalNameList.stream().skip((page - 1) * pageSize).limit(pageSize).collect(Collectors.toList());System.out.println("第"+page+"頁,數量:"+pageList.size()+" 數據:" + pageList);}} }

十、排序操作(Intermediate)

對 Stream 的排序通過 sorted 進行,它比數組的排序更強之處在于你可以首先對 Stream 進行各類 map、filter、limit、skip 甚至 distinct 來減少元素數量后,再排序,這能幫助程序明顯縮短執行時間。

List<String> pList = persons.stream().limit(2).sorted((p1, p2) -> p1.toString().compareTo(p2.toString())).collect(Collectors.toList());

?十一、最大/最小值、去重操作(Intermediate)

min 和 max 的功能也可以通過對 Stream 元素先排序,再 findFirst 來實現,但前者的性能會更好,為 O(n),而 sorted 的成本是 O(n log n)。同時它們作為特殊的 reduce 方法被獨立出來也是因為求最大最小值是很常見的操作。

BufferedReader br = new BufferedReader(new FileReader("c:\\noThisFile.txt")); int longest = br.lines().mapToInt(String::length).max().getAsInt();br.close(); System.out.println(longest);

十二、匹配操作(Terminal)

Stream 有三個 match 方法,從語義上說:

allMatch:Stream 中全部元素符合傳入的 predicate,返回 true?

anyMatch:Stream中只要有一個元素符合傳入的 predicate,返回 true

noneMatch:Stream 中沒有一個元素符合傳入的predicate,返回 true

它們都不是要遍歷全部元素才能返回結果。例如 allMatch 只要一個元素不滿足條件,就 skip 剩下的所有元素,返回 false。

List<Person> persons = new ArrayList(); persons.add(new Person(1, "name" + 1, 10)); persons.add(new Person(2, "name" + 2, 21)); persons.add(new Person(3, "name" + 3, 34)); persons.add(new Person(4, "name" + 4, 6)); persons.add(new Person(5, "name" + 5, 55)); boolean isAllAdult = persons.stream().allMatch(p -> p.getAge() > 18); System.out.println("All are adult? " + isAllAdult); boolean isThereAnyChild = persons.stream().anyMatch(p -> p.getAge() < 12); System.out.println("Any child? " + isThereAnyChild);

十三、常用聚合函數案例

數據準備,聲明一個User對象,然后初始化一個user 列表:

@Data @AllArgsConstructor public static class User implements Jsonable {private Long id;private String name;private Integer age;private String address;private String group;@Overridepublic String toString() {return this.toJsonStr();} }public static final List<User> users = new ArrayList<>();static {User morty = new User(1L, "morty", 28, "昌平區天通中苑", "研發組");User tom = new User(2L, "tom", 24, "朝陽區將臺街道", "研發組");User lucy = new User(3L, "lucy", 22, "朝陽區美景東方小區", "測試組");users.add(morty);users.add(tom);users.add(lucy); }

13.1 提取某一列

List<String> names = users.stream().map(User::getName).collect(Collectors.toList()); System.out.println("names = " + names);// names = [morty, tom, lucy]

13.2?連接某一列

String joiningNames = users.stream().map(User::getName).collect(Collectors.joining("_")); System.out.println("joiningNames = " + joiningNames);// joiningNames = morty_tom_lucy

13.3?映射表

Map<Long, User> userMapping = users.stream().collect(Collectors.toMap(User::getId, o -> o)); System.out.println("userMapping = " + userMapping);// userMapping = {1={"address":"昌平區天通中苑","name":"morty","id":1,"age":28,"group":"研發組"}, 2={"address":"朝陽區將臺街道","name":"tom","id":2,"age":24,"group":"研發組"}, 3={"address":"朝陽區美景東方小區","name":"lucy","id":3,"age":22,"group":"測試組"}}

13.4 按某列分組

Map<String, List<User>> userGroups = users.stream().collect(Collectors.groupingBy(User::getGroup)); System.out.println("userGroups = " + userGroups);// userGroups = {測試組=[{"address":"朝陽區美景東方小區","name":"lucy","id":3,"age":22,"group":"測試組"}], 研發組=[{"address":"昌平區天通中苑","name":"morty","id":1,"age":28,"group":"研發組"}, {"address":"朝陽區將臺街道","name":"tom","id":2,"age":24,"group":"研發組"}]}

鳴謝

《Java 8 中的 Streams API 詳解》

總結

以上是生活随笔為你收集整理的Java8————Stream API的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。