位元組二面被問Java Stream 流操作‘’?看完這篇,教你自信應對

位元組二面被問Java Stream 流操作‘’?看完這篇,教你自信應對

Stream

將要處理的元素集合看作一種流,在流的過程中,藉助

Stream API

對流中的元素進行操作,比如:篩選、排序、聚合等。

位元組二面被問Java Stream 流操作‘’?看完這篇,教你自信應對

image-20210701194245361

Stream

的運算子大體上分為兩種:

中間運算子

終止運算子

中間運算子

對於資料流來說,中間運算子在執行指定處理程式後,資料流依然可以傳遞給下一級的運算子。

中間運算子包含8種(排除了parallel,sequential,這兩個操作並不涉及到對資料流的加工操作):

map(mapToInt,mapToLong,mapToDouble) 轉換運算子,把比如A->B,這裡預設提供了轉int,long,double的運算子。

flatmap(flatmapToInt,flatmapToLong,flatmapToDouble) 拍平操作比如把 int[]{2,3,4} 拍平 變成 2,3,4 也就是從原來的一個數據變成了3個數據,這裡預設提供了拍平成int,long,double的運算子。

limit 限流操作,比如資料流中有10個 我只要出前3個就可以使用。

distint 去重操作,對重複元素去重,底層使用了equals方法。

filter 過濾操作,把不想要的資料過濾。

peek 挑出操作,如果想對資料進行某些操作,如:讀取、編輯修改等。

skip 跳過操作,跳過某些元素。

sorted(unordered) 排序操作,對元素排序,前提是實現Comparable介面,當然也可以自定義比較器。

終止運算子

資料經過中間加工操作,就輪到終止運算子上場了;

終止運算子就是用來對資料進行收集或者消費的,資料到了終止操作這裡就不會向下流動了,終止運算子只能使用一次。

collect 收集操作,將所有資料收集起來,這個操作非常重要,官方的提供的Collectors 提供了非常多收集器,可以說Stream 的核心在於Collectors。

count 統計操作,統計最終的資料個數。

findFirst、findAny 查詢操作,查詢第一個、查詢任何一個 返回的型別為Optional。

noneMatch、allMatch、anyMatch 匹配操作,資料流中是否存在符合條件的元素 返回值為bool 值。

min、max 最值操作,需要自定義比較器,返回資料流中最大最小的值。

reduce 規約操作,將整個資料流的值規約為一個值,count、min、max底層就是使用reduce。

forEach、forEachOrdered 遍歷操作,這裡就是對最終的資料進行消費了。

toArray 陣列操作,將資料流的元素轉換成陣列。

Stream的建立

1、透過

java。util。Collection。stream()

方法用集合建立流

List list = Arrays。asList(“a”, “b”, “c”);// 建立一個順序流Stream stream = list。stream();// 建立一個並行流Stream parallelStream = list。parallelStream();

2、使用

java。util。Arrays。stream(T[] array)

方法用陣列建立流

int[] array={1,3,5,6,8};IntStream stream = Arrays。stream(array);

3、使用

Stream

的靜態方法:

of()、iterate()、generate()

Stream stream = Stream。of(1, 2, 3, 4, 5, 6);Stream stream2 = Stream。iterate(0, (x) -> x + 3)。limit(4);stream2。forEach(System。out::println); // 0 3 6 9Stream stream3 = Stream。generate(Math::random)。limit(3);stream3。forEach(System。out::println);

輸出結果:

3690。81066234426861140。115546437273884580。1404645961428974Process finished with exit code 0

stream

parallelStream

的簡單區分:

stream

是順序流,由主執行緒按順序對流執行操作;

parallelStream

是並行流,內部以多執行緒並行執行的方式對流進行操作,但前提是流中的資料處理沒有順序要求。

例如篩選集合中的奇數,兩者的處理不同之處:

位元組二面被問Java Stream 流操作‘’?看完這篇,教你自信應對

image-20210701230623951

Stream使用

遍歷/匹配(foreach/find/match)

Stream

也是支援類似集合的遍歷和匹配元素的,只是

Stream

中的元素是以

Optional

型別存在的。

Stream

的遍歷、匹配非常簡單。

public class StreamTest { public static void main(String[] args) { List list = Arrays。asList(7, 6, 9, 3, 8, 2, 1); // 遍歷輸出符合條件的元素 list。stream()。filter(x -> x > 6)。forEach(System。out::println); // 匹配第一個 Optional findFirst = list。stream()。filter(x -> x > 6)。findFirst(); // 匹配任意(適用於並行流) Optional findAny = list。parallelStream()。filter(x -> x > 6)。findAny(); // 是否包含符合特定條件的元素 boolean anyMatch = list。stream()。anyMatch(x -> x < 6); System。out。println(“匹配第一個值:” + findFirst。get()); System。out。println(“匹配任意一個值:” + findAny。get()); System。out。println(“是否存在大於6的值:” + anyMatch); }}

輸出結果:

798匹配第一個值:7匹配任意一個值:8是否存在大於6的值:trueProcess finished with exit code 0

篩選(filter)

篩選,是按照一定的規則校驗流中的元素,將符合條件的元素提取到新的流中的操作。

篩選出Integer集合中大於7的元素,並打印出來

public class StreamTest { public static void main(String[] args) { List list = Arrays。asList(6, 7, 3, 8, 1, 2, 9); Stream stream = list。stream(); stream。filter(x -> x > 7)。forEach(System。out::println); }}

輸出結果:

89Process finished with exit code 0

聚合(max/min/count)

max

min

count

這些字眼你一定不陌生,沒錯,在mysql中我們常用它們進行資料統計。Java stream中也引入了這些概念和用法,極大地方便了我們對集合、陣列的資料統計工作。

案例一:獲取String集合中最長的元素。

public class StreamTest { public static void main(String[] args) { List list = Arrays。asList(“adnm”, “admmt”, “pot”, “xbangd”, “weoujgsd”); Optional max = list。stream()。max(Comparator。comparing(String::length)); System。out。println(“最長的字串:” + max。get()); }}

輸出結果:

最長的字串:weoujgsdProcess finished with exit code 0

案例二:獲取Integer集合中的最大值。

public class StreamTest { public static void main(String[] args) { List list = Arrays。asList(7, 6, 9, 4, 11, 6); // 自然排序 Optional max = list。stream()。max(Integer::compareTo); // 自定義排序 Optional max2 = list。stream()。max(new Comparator() { @Override public int compare(Integer o1, Integer o2) { return o1。compareTo(o2); } }); System。out。println(“自然排序的最大值:” + max。get()); System。out。println(“自定義排序的最大值:” + max2。get()); }}

輸出結果:

自然排序的最大值:11自定義排序的最大值:11Process finished with exit code 0

案例三:計算Integer集合中大於6的元素的個數。

public class StreamTest { public static void main(String[] args) { List list = Arrays。asList(7, 6, 4, 8, 2, 11, 9); long count = list。stream()。filter(x -> x > 6)。count(); System。out。println(“list中大於6的元素個數:” + count); }}

輸出結果:

list中大於6的元素個數:4Process finished with exit code 0

對映(map/flatMap)

對映,可以將一個流的元素按照一定的對映規則對映到另一個流中。分為

map

flatMap

map

:接收一個函式作為引數,該函式會被應用到每個元素上,並將其對映成一個新的元素。

flatMap

:接收一個函式作為引數,將流中的每個值都換成另一個流,然後把所有流連線成一個流。

案例一:英文字串陣列的元素全部改為大寫。整數陣列每個元素+3。

public class StreamTest { public static void main(String[] args) { String[] strArr = { “abcd”, “bcdd”, “defde”, “fTr” }; List strList = Arrays。stream(strArr)。map(String::toUpperCase)。collect(Collectors。toList()); System。out。println(“每個元素大寫:” + strList); List intList = Arrays。asList(1, 3, 5, 7, 9, 11); List intListNew = intList。stream()。map(x -> x + 3)。collect(Collectors。toList()); System。out。println(“每個元素+3:” + intListNew); }}

輸出結果:

每個元素大寫:[ABCD, BCDD, DEFDE, FTR]每個元素+3:[4, 6, 8, 10, 12, 14]Process finished with exit code 0

案例二:將兩個字元數組合併成一個新的字元陣列。

public class StreamTest { public static void main(String[] args) { List list = Arrays。asList(“m,k,l,a”, “1,3,5,7”); List listNew = list。stream()。flatMap(s -> { // 將每個元素轉換成一個stream String[] split = s。split(“,”); Stream s2 = Arrays。stream(split); return s2; })。collect(Collectors。toList()); System。out。println(“處理前的集合:” + list); System。out。println(“處理後的集合:” + listNew); }}

輸出結果:

處理前的集合:[m,k,l,a, 1,3,5,7]處理後的集合:[m, k, l, a, 1, 3, 5, 7]Process finished with exit code 0

歸約(reduce)

歸約,也稱縮減,顧名思義,是把一個流縮減成一個值,能實現對集合求和、求乘積和求最值操作。

案例一:求Integer集合的元素之和、乘積和最大值。

public class StreamTest { public static void main(String[] args) { List list = Arrays。asList(1, 3, 2, 8, 11, 4); // 求和方式1 Optional sum = list。stream()。reduce(Integer::sum); // 求和方式2 Optional sum2 = list。stream()。reduce(Integer::sum); // 求和方式3 Integer sum3 = list。stream()。reduce(0, Integer::sum); // 求乘積 Optional product = list。stream()。reduce((x, y) -> x * y); // 求最大值方式1 Optional max = list。stream()。reduce((x, y) -> x > y ? x : y); // 求最大值寫法2 Integer max2 = list。stream()。reduce(1, Integer::max); System。out。println(“list求和:” + sum。get() + “,” + sum2。get() + “,” + sum3); System。out。println(“list求積:” + product。get()); System。out。println(“list求和:” + max。get() + “,” + max2); }}

輸出結果:

list求和:29,29,29list求積:2112list求和:11,11Process finished with exit code 0

歸集(toList/toSet/toMap)

因為流不儲存資料,那麼在流中的資料完成處理後,需要將流中的資料重新歸集到新的集合裡。

toList

toSet

toMap

比較常用,另外還有

toCollection

toConcurrentMap

等複雜一些的用法。

下面用一個案例演示

toList

toSet

toMap

public class Person { private String name; // 姓名 private int salary; // 薪資 private int age; // 年齡 private String sex; //性別 private String area; // 地區 // 構造方法 public Person(String name, int salary, int age,String sex,String area) { this。name = name; this。salary = salary; this。age = age; this。sex = sex; this。area = area; } public String getName() { return name; } public void setName(String name) { this。name = name; } public int getSalary() { return salary; } public void setSalary(int salary) { this。salary = salary; } public int getAge() { return age; } public void setAge(int age) { this。age = age; } public String getSex() { return sex; } public void setSex(String sex) { this。sex = sex; } public String getArea() { return area; } public void setArea(String area) { this。area = area; } @Override public String toString() { return “Person{” + “name=‘” + name + ’\‘’ + “, salary=” + salary + “, age=” + age + “, sex=‘” + sex + ’\‘’ + “, area=‘” + area + ’\‘’ + ‘}’; }}

public class StreamTest { public static void main(String[] args) { List list = Arrays。asList(1, 6, 3, 4, 6, 7, 9, 6, 20); List listNew = list。stream()。filter(x -> x % 2 == 0)。collect(Collectors。toList()); Set set = list。stream()。filter(x -> x % 2 == 0)。collect(Collectors。toSet()); List personList = new ArrayList(); personList。add(new Person(“Tom”, 8900, 23, “male”, “New York”)); personList。add(new Person(“Jack”, 7000, 25, “male”, “Washington”)); personList。add(new Person(“Lily”, 7800, 21, “female”, “Washington”)); personList。add(new Person(“Anni”, 8200, 24, “female”, “New York”)); Map<?, Person> map = personList。stream()。filter(p -> p。getSalary() > 8000) 。collect(Collectors。toMap(Person::getName, p -> p)); System。out。println(“toList:” + listNew); System。out。println(“toSet:” + set); System。out。println(“toMap:” + map); }}

輸出結果:

toList:[6, 4, 6, 6, 20]toSet:[4, 20, 6]toMap:{Tom=Person{name=‘Tom’, salary=8900, age=23, sex=‘male’, area=‘New York’}, Anni=Person{name=‘Anni’, salary=8200, age=24, sex=‘female’, area=‘New York’}}Process finished with exit code 0

統計(count/averaging)

Collectors

提供了一系列用於資料統計的靜態方法:

計數:

count

平均值:

averagingInt

averagingLong

averagingDouble

最值:

maxBy

minBy

求和:

summingInt

summingLong

summingDouble

統計以上所有:

summarizingInt

summarizingLong

summarizingDouble

案例:統計員工人數、平均工資、工資總額、最高工資。

public class StreamTest { public static void main(String[] args) { List personList = new ArrayList(); personList。add(new Person(“Tom”, 8900, 23, “male”, “New York”)); personList。add(new Person(“Jack”, 7000, 25, “male”, “Washington”)); personList。add(new Person(“Lily”, 7800, 21, “female”, “Washington”)); // 求總數 long count = personList。size(); // 求平均工資 Double average = personList。stream()。collect(Collectors。averagingDouble(Person::getSalary)); // 求最高工資 Optional max = personList。stream()。map(Person::getSalary)。max(Integer::compare); // 求工資之和 int sum = personList。stream()。mapToInt(Person::getSalary)。sum(); // 一次性統計所有資訊 DoubleSummaryStatistics collect = personList。stream()。collect(Collectors。summarizingDouble(Person::getSalary)); System。out。println(“員工總數:” + count); System。out。println(“員工平均工資:” + average); System。out。println(“員工最高工資:” + max。get()); System。out。println(“員工工資總和:” + sum); System。out。println(“員工工資所有統計:” + collect); }}

輸出結果:

員工總數:3員工平均工資:7900。0員工最高工資:8900員工工資總和:23700員工工資所有統計:DoubleSummaryStatistics{count=3, sum=23700。000000, min=7000。000000, average=7900。000000, max=8900。000000}Process finished with exit code 0

分組(partitioningBy/groupingBy)

分割槽:將

stream

按條件分為兩個

Map

,比如員工按薪資是否高於8000分為兩部分。

分組:將集合分為多個Map,比如員工按性別分組。有單級分組和多級分組。

案例:將員工按薪資是否高於8000分為兩部分;將員工按性別和地區分組

public class StreamTest { public static void main(String[] args) { List personList = new ArrayList(); personList。add(new Person(“Tom”, 8900, 23, “male”, “Washington”)); personList。add(new Person(“Jack”, 7000, 25, “male”, “Washington”)); personList。add(new Person(“Lily”, 7800, 21, “female”, “New York”)); personList。add(new Person(“Anni”, 8200, 24, “female”, “New York”)); // 將員工按薪資是否高於8000分組 Map> part = personList。stream()。collect(Collectors。partitioningBy(x -> x。getSalary() > 8000)); // 將員工按性別分組 Map> group = personList。stream()。collect(Collectors。groupingBy(Person::getSex)); // 將員工先按性別分組,再按地區分組 Map>> group2 = personList。stream()。collect(Collectors。groupingBy(Person::getSex, Collectors。groupingBy(Person::getArea))); System。out。println(“員工按薪資是否大於8000分組情況:” + part); System。out。println(“員工按性別分組情況:” + group); System。out。println(“員工按性別、地區:” + group2); }}

輸出結果:

員工按薪資是否大於8000分組情況:{false=[Person{name=‘Jack’, salary=7000, age=25, sex=‘male’, area=‘Washington’}, Person{name=‘Lily’, salary=7800, age=21, sex=‘female’, area=‘New York’}], true=[Person{name=‘Tom’, salary=8900, age=23, sex=‘male’, area=‘Washington’}, Person{name=‘Anni’, salary=8200, age=24, sex=‘female’, area=‘New York’}]}員工按性別分組情況:{female=[Person{name=‘Lily’, salary=7800, age=21, sex=‘female’, area=‘New York’}, Person{name=‘Anni’, salary=8200, age=24, sex=‘female’, area=‘New York’}], male=[Person{name=‘Tom’, salary=8900, age=23, sex=‘male’, area=‘Washington’}, Person{name=‘Jack’, salary=7000, age=25, sex=‘male’, area=‘Washington’}]}員工按性別、地區:{female={New York=[Person{name=‘Lily’, salary=7800, age=21, sex=‘female’, area=‘New York’}, Person{name=‘Anni’, salary=8200, age=24, sex=‘female’, area=‘New York’}]}, male={Washington=[Person{name=‘Tom’, salary=8900, age=23, sex=‘male’, area=‘Washington’}, Person{name=‘Jack’, salary=7000, age=25, sex=‘male’, area=‘Washington’}]}}Process finished with exit code 0

接合(joining)

joining

可以將stream中的元素用特定的連線符(沒有的話,則直接連線)連線成一個字串。

public class StreamTest { public static void main(String[] args) { List personList = new ArrayList(); personList。add(new Person(“Tom”, 8900, 23, “male”, “New York”)); personList。add(new Person(“Jack”, 7000, 25, “male”, “Washington”)); personList。add(new Person(“Lily”, 7800, 21, “female”, “Washington”)); String names = personList。stream()。map(Person::getName)。collect(Collectors。joining(“,”)); System。out。println(“所有員工的姓名:” + names); List list = Arrays。asList(“A”, “B”, “C”); String string = list。stream()。collect(Collectors。joining(“-”)); System。out。println(“拼接後的字串:” + string); }}

輸出結果:

所有員工的姓名:Tom,Jack,Lily拼接後的字串:A-B-CProcess finished with exit code 0

排序(sorted)

sorted

,中間操作。有兩種排序:

sorted()

:自然排序,流中元素需實現

Comparable

介面

sorted(Comparator com)

Comparator

排序器自定義排序

案例:將員工按工資由高到低(工資一樣則按年齡由大到小)排序

public class StreamTest { public static void main(String[] args) { List personList = new ArrayList(); personList。add(new Person(“Sherry”, 9000, 24, “female”, “New York”)); personList。add(new Person(“Tom”, 8900, 22, “male”, “Washington”)); personList。add(new Person(“Jack”, 9000, 25, “male”, “Washington”)); personList。add(new Person(“Lily”, 8800, 26, “male”, “New York”)); personList。add(new Person(“Alisa”, 9000, 26, “female”, “New York”)); // 按工資升序排序(自然排序) List newList = personList。stream()。sorted(Comparator。comparing(Person::getSalary))。map(Person::getName) 。collect(Collectors。toList()); // 按工資倒序排序 List newList2 = personList。stream()。sorted(Comparator。comparing(Person::getSalary)。reversed()) 。map(Person::getName)。collect(Collectors。toList()); // 先按工資再按年齡升序排序 List newList3 = personList。stream() 。sorted(Comparator。comparing(Person::getSalary)。thenComparing(Person::getAge))。map(Person::getName) 。collect(Collectors。toList()); // 先按工資再按年齡自定義排序(降序) List newList4 = personList。stream()。sorted((p1, p2) -> { if (p1。getSalary() == p2。getSalary()) { return p2。getAge() - p1。getAge(); } else { return p2。getSalary() - p1。getSalary(); } })。map(Person::getName)。collect(Collectors。toList()); System。out。println(“按工資升序排序:” + newList); System。out。println(“按工資降序排序:” + newList2); System。out。println(“先按工資再按年齡升序排序:” + newList3); System。out。println(“先按工資再按年齡自定義降序排序:” + newList4); }}

輸出結果:

按工資升序排序:[Lily, Tom, Sherry, Jack, Alisa]按工資降序排序:[Sherry, Jack, Alisa, Tom, Lily]先按工資再按年齡升序排序:[Lily, Tom, Sherry, Jack, Alisa]先按工資再按年齡自定義降序排序:[Alisa, Jack, Sherry, Tom, Lily]Process finished with exit code 0

提取/組合

流也可以進行合併、去重、限制、跳過等操作。

public class StreamTest { public static void main(String[] args) { String[] arr1 = { “a”, “b”, “c”, “d” }; String[] arr2 = { “d”, “e”, “f”, “g” }; Stream stream1 = Stream。of(arr1); Stream stream2 = Stream。of(arr2); // concat:合併兩個流 distinct:去重 List newList = Stream。concat(stream1, stream2)。distinct()。collect(Collectors。toList()); // limit:限制從流中獲得前n個數據 List collect = Stream。iterate(1, x -> x + 2)。limit(10)。collect(Collectors。toList()); // skip:跳過前n個數據 List collect2 = Stream。iterate(1, x -> x + 2)。skip(1)。limit(5)。collect(Collectors。toList()); System。out。println(“流合併:” + newList); System。out。println(“limit:” + collect); System。out。println(“skip:” + collect2); }}

輸出結果:

流合併:[a, b, c, d, e, f, g]limit:[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]skip:[3, 5, 7, 9, 11]Process finished with exit code 0

分頁操作

stream api 的強大之處還不僅僅是對集合進行各種組合操作,還支援分頁操作。

例如,將如下的陣列從小到大進行排序,排序完成之後,從第1行開始,查詢10條資料出來,操作如下:

//需要查詢的資料List numbers = Arrays。asList(3, 2, 2, 3, 7, 3, 5, 10, 6, 20, 30, 40, 50, 60, 100);List dataList = numbers。stream()。sorted(Integer::compareTo)。skip(0)。limit(10)。collect(Collectors。toList());System。out。println(dataList。toString());

輸出結果:

[2, 2, 3, 3, 3, 5, 6, 7, 10, 20]Process finished with exit code 0

並行操作

所謂並行,指的是多個任務在同一時間點發生,並由不同的cpu進行處理,不互相搶佔資源;而併發,指的是多個任務在同一時間點內同時發生了,但由同一個cpu進行處理,互相搶佔資源。

stream api 的並行操作和序列操作,只有一個方法區別,其他都一樣,例如下面我們使用parallelStream來輸出空字串的數量:

List strings = Arrays。asList(“abc”, “”, “bc”, “efg”, “abcd”, “”, “jkl”);// 採用平行計算方法,獲取空字串的數量long count = strings。parallelStream()。filter(String::isEmpty)。count();System。out。println(count);

在實際使用的時候,並行操作不一定比序列操作快!對於簡單操作,數量非常大,同時伺服器是多核的話,建議使用Stream並行!反之,採用序列操作更可靠!

集合轉Map操作

在實際的開發過程中,還有一個使用最頻繁的操作就是,將集合元素中某個主鍵欄位作為key,元素作為value,來實現集合轉map的需求,這種需求在資料組裝方面使用的非常多。

public static void main(String[] args) { List personList = new ArrayList<>(); personList。add(new Person(“Tom”,7000,25,“male”,“安徽”)); personList。add(new Person(“Jack”,8000,30,“female”,“北京”)); personList。add(new Person(“Lucy”,9000,40,“male”,“上海”)); personList。add(new Person(“Airs”,10000,40,“female”,“深圳”)); Map collect = personList。stream()。collect(Collectors。toMap(Person::getAge, v -> v, (k1, k2) -> k1)); System。out。println(collect);}

輸出結果:

{40=Person{name=‘Lucy’, salary=9000, age=40, sex=‘male’, area=‘上海’}, 25=Person{name=‘Tom’, salary=7000, age=25, sex=‘male’, area=‘安徽’}, 30=Person{name=‘Jack’, salary=8000, age=30, sex=‘female’, area=‘北京’}}Process finished with exit code 0

開啟

Collectors。toMap

方法原始碼,一起來看看。

public static Collector> toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator mergeFunction) { return toMap(keyMapper, valueMapper, mergeFunction, HashMap::new);}

從引數表可以看出:

第一個引數:表示 key

第二個引數:表示 value

第三個引數:表示某種規則

上文中的

Collectors。toMap(Person::getAge, v -> v, (k1,k2) -> k1)

,表達的意思就是將

age

的內容作為

key

v -> v

是表示將元素

person

作為

value

,其中

(k1,k2) -> k1

表示如果存在相同的

key

,將第一個匹配的元素作為內容,第二個捨棄!

標籤: [Java]

位元組二面被問Java Stream 流操作‘’?看完這篇,教你自信應對