5分鐘讓你快速掌握java8 stream常用開發(fā)技巧
前言
如果有些朋友以前沒有使用過java8 stream這種鏈式編程方式做開發(fā),想學習一下。
如果有些朋友只學習了一部分用法,想學習更多。
如果有些朋友想看看有沒有好的示例適用于實際工作當中。
那么恭喜你,這篇文章非常適合你。
首先,我們一起看看stream的繼承關系:

Stream、IntStream、LongStream、DoubleStream的父接口都是BaseStream。BaseStream的四個子接口方法都差不多,只是IntStream、LongStream、DoubleStream直接存儲基本類型,可以避免自動裝/拆箱,效率會更高一些。但是,我們實際上使用Stream更多一些。
我們再看看stream的工作流程圖:

為什么要學stream的鏈式編程方式
業(yè)務需求1:指定一個字符串數組,找出里面相同的元素,并且統(tǒng)計重復的次數。
我們以前大概是這樣做的:
public class CountTest {
@Test
public void testCount1() {
List<String> list = Lists.newArrayList("a", "b", "ab", "abc", "a", "ab", "a", "abcd", "bd", "abc");
Map<String, Long> countMap = new HashMap<>();
for (String data : list) {
Long aLong = countMap.get(data);
if (Objects.isNull(aLong)) {
countMap.put(data, 1L);
} else {
countMap.put(data, ++aLong);
}
}
countMap.forEach((key, value) -> System.out.println("key:" + key + " value:" + value));
}
}
執(zhí)行結果:
key:a value:3
key:ab value:2
key:b value:1
key:bd value:1
key:abc value:2
key:abcd value:1
我們再看看如果用java8的stream可以怎么做:
public class CountTest {
@Test
public void testCount2() {
List<String> list = Lists.newArrayList("a", "b", "ab", "abc", "a", "ab", "a", "abcd", "bd", "abc");
Map<String, Long> countMap = list.stream().collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
countMap.forEach((key, value) -> System.out.println("key:" + key + " value:" + value));
}
}
執(zhí)行結果:
key:a value:3
key:ab value:2
key:b value:1
key:bd value:1
key:abc value:2
key:abcd value:1
我們可以看到testCount1和testCount2執(zhí)行結果相同,僅僅一行代碼:
Map<String, Long> countMap = list.stream().collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
就可以實現上面testCount1中多行代碼的邏輯。
業(yè)務需求2:從一個指定的字符串數組中,查找指定的字符串是否存在
我們以前大概是這樣做的:
public class FindTest {
@Test
public void testFind1() {
String findStr = "bd";
List<String> list = Lists.newArrayList("a", "b", "ab", "abc", "a", "ab", "a", "abcd", "bd", "abc");
boolean match = false;
for (String data : list) {
if (data.equals(findStr)) {
match = true;
break;
}
}
//結果:match:true
System.out.println("match:" + match);
}
}
我們再看看如果用java8的stream可以怎么做:
public class MatchTest {
@Test
public void testFind2() {
String findStr = "bd";
List<String> list = Lists.newArrayList("a", "b", "ab", "abc", "a", "ab", "a", "abcd", "bd", "abc");
boolean match = list.stream().anyMatch(x -> x.equals(findStr));
//結果:match:true
System.out.println("match:" + match);
}
}
我們可以看到調用testFind1和testFind2方法執(zhí)行結果也是一樣的。但是,用java8 stream的語法,又只用一行代碼就完成功能了,真棒。
java8 stream超詳細用法指南
stream的操作符大體上分為兩種:中間操作符和終止操作符
中間操作:
1.filter(T-> boolean)
過濾數據,保留 boolean 為 true 的元素,返回一個集合
public class FilterTest {
@Test
public void testFilter() {
List<Integer> list = Lists.newArrayList(20, 23, 25, 28, 30, 33, 37, 40);
//從指定數據集合中過濾出大于等于30的數據集合
List<Integer> collect = list.stream().filter(x -> x >= 30).collect(Collectors.toList());
//結果:[33, 37, 40]
System.out.println(collect);
}
}
collect(Collectors.toList())可以把流轉換為 List 類型,collect實際上是一個終止操作。
2.map(T -> R)
轉換操作符,可以做數據轉換,比如:把字符串轉換成int、long、double,或者把一個實體轉換成另外一個實體。包含:map,mapToInt、mapToLong、mapToDouble
public class MapTest {
@Test
public void testMap() {
List<String> list = Lists.newArrayList("1", "2", "3", "4", "5", "6");
List<Long> collect1 = list.stream().map(x -> Long.parseLong(x)).collect(Collectors.toList());
//結果:[1, 2, 3, 4, 5, 6]
System.out.println(collect1);
//結果:111111
list.stream().mapToInt(x -> x.length()).forEach(System.out::print);
System.out.println("");
//結果:111111
list.stream().mapToLong(x -> x.length()).forEach(System.out::print);
System.out.println("");
//結果:1.01.01.01.01.01.0
list.stream().mapToDouble(x -> x.length()).forEach(System.out::print);
}
}
3.flatMap(T -> Stream)
將流中的每一個元素 T 映射為一個流,再把每一個流連接成為一個流
public class FlatMapTest {
@Test
public void testFlatMap() {
List<List<String>> list = new ArrayList<List<String>>(){{
add(Lists.newArrayList("a","b","c"));
add(Lists.newArrayList("d","e","f"));
add(Lists.newArrayList("j","k","y"));
}};
//結果:[[a, b, c], [d, e, f], [j, k, y]]
System.out.println(list);
List<String> collect = list.stream().flatMap(List::stream).collect(Collectors.toList());
//結果:[a, b, c, d, e, f, j, k, y]
System.out.println(collect);
}
}
我們可以看到flatMap可以輕松把字符串的二維數據變成一位數組。
4.distinct
去重,類似于msql中的distinct的作用,底層使用了equals方法做比較。
public class DistinctTest {
@Test
public void testDistinct() {
List<String> list = Lists.newArrayList("a", "b", "ab", "abc", "a", "ab", "a", "abcd", "bd", "abc");
List<String> collect = list.stream().distinct().collect(Collectors.toList());
//結果:[a, b, ab, abc, abcd, bd]
System.out.println(collect);
}
}
其實,去重還有另外一種辦法,可以用Collectors.toSet(),后面會講到。
5.sorted
對元素進行排序,前提是實現Comparable接口,當然也可以自定義比較器。
public class SortTest {
@Test
public void testSort() {
List<Integer> list = Lists.newArrayList(5, 3, 7, 1, 4, 6);
List<Integer> collect = list.stream().sorted((a, b) -> a.compareTo(b)).collect(Collectors.toList());
//結果:[1, 3, 4, 5, 6, 7]
System.out.println(collect);
}
}
6.limit
限流操作,有點類似于mysql中的limit功能,比如:有10個元素,只取前面3個元素
public class LimitTest {
@Test
public void testLimit() {
List<String> list = Lists.newArrayList("a", "b", "ab", "abc", "a", "ab", "a", "abcd", "bd", "abc");
List<String> collect = list.stream().limit(3).collect(Collectors.toList());
//結果:[a, b, ab]
System.out.println(collect);
}
}
7.skip
跳過操作,比如:有個10個元素,從第5個元素開始去后面的元素
public class SkipTest {
@Test
public void testSkip() {
List<String> list = Lists.newArrayList("a", "b", "ab", "abc", "a", "ab", "a", "abcd", "bd", "abc");
List<String> collect = list.stream().skip(5).collect(Collectors.toList());
//結果:[ab, a, abcd, bd, abc]
System.out.println(collect);
}
}
8.peek
挑出操作,
public class PeekTest {
@Test
public void testPeek() {
List<String> list = Lists.newArrayList("a", "b", "ab", "abc", "a", "ab", "a", "abcd", "bd", "abc");
//結果:abababcaabaabcdbdabc
list.stream().peek(x -> x.toUpperCase()).forEach(System.out::print);
}
}
眼尖的朋友會發(fā)現,進行x.toUpperCase()轉換為大寫功能,但是實際上沒有生效。把peek改成map方法試試:
public class PeekTest {
@Test
public void testPeek() {
List<String> list = Lists.newArrayList("a", "b", "ab", "abc", "a", "ab", "a", "abcd", "bd", "abc");
//結果:ABABABCAABAABCDBDABC
list.stream().map(x -> x.toUpperCase()).forEach(System.out::print);
}
}
我們可以看到,用map操作轉換成大寫功能生效了,但是用peek操作卻沒有生效。peek只是對Stream中的元素進行某些操作,但是操作之后的數據并不返回到Stream中,所以Stream中的元素還是原來的元素。
終止操作:
1.forEach
遍歷操作,包含:forEach 和 forEachOrdered
forEach:支持并行處理
forEachOrdered:是按順序處理的,遍歷速度較慢
public class ForEachTest {
@Test
public void testForEach() {
List<String> list = Lists.newArrayList("a", "b", "ab");
//結果:a b ab
list.stream().forEach(x-> System.out.print(x+' '));
System.out.println("");
//可以簡化
//結果:a b ab
list.forEach(x-> System.out.print(x+' '));
System.out.println("");
//結果:a b ab
list.stream().forEachOrdered(x-> System.out.print(x+' '));
}
}
2.collect
收集操作,將所有的元素收集起來,Collectors 提供了非常多收集器。包含:toMap、toSet、toList、joining,groupingBy,maxBy,minBy等操作。
toMap:將數據流轉換為map,里面包含的元素是用key/value的形式的
toSet:將數據流轉換為set,里面包含的元素不可重復
toList:將數據流轉出為list,里面包含的元素是有序的
joining:拼接字符串
groupingBy:分組,可以將list轉換map
couting:統(tǒng)計元素數量
maxBy:獲取最大元素
minBy:獲取最小元素
summarizingInt: 匯總int類型的元素,返回IntSummaryStatistics,再調用具體的方法對元素進行統(tǒng)計:getCount(統(tǒng)計數量),getSum(求和),getMin(獲取最小值),getMax(獲取最大值),getAverage(獲取平均值)
summarizingLong:匯總long類型的元素,用法同summarizingInt
summarizingDouble:匯總double類型的元素,用法同summarizingInt
averagingInt:獲取int類型的元素的平均值,返回一個double類型的數據
averagingLong:獲取long類型的元素的平均值,用法同averagingInt
averagingDouble:獲取double類型的元素的平均值,用法同averagingInt
mapping:獲取映射,可以將原始元素的一部分內容作為一個新元素返回
public class CollectTest {
@Data
@AllArgsConstructor
class User {
private String name;
private Integer age;
}
@Test
public void testCollect() {
List<String> list0 = Lists.newArrayList("a", "b", "ab");
Map<String, String> collect0 = list0.stream().collect(Collectors.toMap(String::new, Function.identity()));
//結果:{ab=ab, a=a, b=b}
System.out.println(collect0);
List<String> list = Lists.newArrayList("a", "b", "ab", "a", "b", "ab");
List<String> collect1 = list.stream().collect(Collectors.toList());
//結果:[a, b, ab, a, b, ab]
System.out.println(collect1);
//結果:[a, ab, b]
Set<String> collect2 = list.stream().collect(Collectors.toSet());
System.out.println(collect2);
String collect3 = list.stream().collect(Collectors.joining(","));
//結果:a,b,ab,a,b,ab
System.out.println(collect3);
Map<String, List<String>> collect4 = list.stream().collect(Collectors.groupingBy(Function.identity()));
//結果:{ab=[ab, ab], a=[a, a], b=[b, b]}
System.out.println(collect4);
Long collect = list.stream().collect(Collectors.counting());
//結果:6
System.out.println(collect);
String collect5 = list.stream().collect(Collectors.maxBy((a, b) -> a.compareTo(b))).orElse(null);
//結果:b
System.out.println(collect5);
String collect6 = list.stream().collect(Collectors.minBy((a, b) -> a.compareTo(b))).orElse(null);
//結果:a
System.out.println(collect6);
List<String> list2 = Lists.newArrayList("2", "3", "5");
IntSummaryStatistics summaryStatistics = list2.stream().collect(Collectors.summarizingInt(x -> Integer.parseInt(x)));
long sum = summaryStatistics.getSum();
//結果:10
System.out.println(sum);
Double collect7 = list2.stream().collect(Collectors.averagingInt(x -> Integer.parseInt(x)));
//結果:3.3333333333333335
System.out.println(collect7);
List<User> userList = new ArrayList<User>() {{
add(new User("jack",23));
add(new User("james",30));
add(new User("curry",28));
}};
List<String> collect8 = userList.stream().collect(Collectors.mapping(User::getName, Collectors.toList()));
//[jack, james, curry]
System.out.println(collect8);
}
}
3.find
查找操作,包含:findFirst、findAny
findFirst:找到第一個,返回的類型為Optional
findAny:使用 stream() 時找到的是第一個元素,使用 parallelStream() 并行時找到的是其中一個元素,返回的類型為Optional
public class FindOpTest {
@Test
public void testFindOp() {
List<String> list = Lists.newArrayList("a", "b", "ab", "abc", "bc", "ab");
//查找第一匹配的元素
String data1 = list.stream().findFirst().orElse(null);
//結果: a
System.out.println(data1);
String data2 = list.stream().findAny().orElse(null);
//結果: a
System.out.println(data2);
}
}
4.match
匹配操作,包含:allMatch、anyMatch、noneMatch
allMatch:所有元素都滿足條件,返回boolean類型
anyMatch:任意一個元素滿足條件,返回boolean類型
noneMatch:所有元素都不滿足條件,返回boolean類型
public class MatchTest {
@Test
public void testMatch() {
List<Integer> list = Lists.newArrayList(2, 3, 5, 7);
boolean allMatch = list.stream().allMatch(x -> x > 1);
//結果:true
System.out.println(allMatch);
boolean allMatch2 = list.stream().allMatch(x -> x > 2);
//結果:false
System.out.println(allMatch2);
boolean anyMatch = list.stream().anyMatch(x -> x > 2);
//結果:true
System.out.println(anyMatch);
boolean noneMatch1 = list.stream().noneMatch(x -> x > 5);
//結果:false
System.out.println(noneMatch1);
boolean noneMatch2 = list.stream().noneMatch(x -> x > 7);
//結果:true
System.out.println(noneMatch2);
}
}
5.count
統(tǒng)計操作,效果跟調用集合的size()方法類似
public class CountOpTest {
@Test
public void testCountOp() {
List<String> list = Lists.newArrayList("a", "b", "ab");
long count = list.stream().count();
//結果:3
System.out.println(count);
}
}
6.min、max
min:獲取最小值,返回Optional類型的數據
max:獲取最大值,返回Optional類型的數據
public class MaxMinTest {
@Test
public void testMaxMin() {
List<Integer> list = Lists.newArrayList(2, 3, 5, 7);
Optional<Integer> max = list.stream().max((a, b) -> a.compareTo(b));
//結果:7
System.out.println(max.get());
Optional<Integer> min = list.stream().min((a, b) -> a.compareTo(b));
//結果:2
System.out.println(min.get());
}
}
7.reduce
規(guī)約操作,將整個數據流的值規(guī)約為一個值,count、min、max底層就是使用reduce。
reduce 操作可以實現從Stream中生成一個值,其生成的值不是隨意的,而是根據指定的計算模型。
public class ReduceTest {
@Test
public void testReduce() {
List<Integer> list = Lists.newArrayList(2, 3, 5, 7);
Integer sum1 = list.stream().reduce(0, Integer::sum);
//結果:17
System.out.println(sum1);
Optional<Integer> reduce = list.stream().reduce((a, b) -> a + b);
//結果:17
System.out.println(reduce.get());
Integer max = list.stream().reduce(0, Integer::max);
//結果:7
System.out.println(max);
Integer min = list.stream().reduce(0, Integer::min);
//結果:0
System.out.println(min);
Optional<Integer> reduce1 = list.stream().reduce((a, b) -> a > b ? b : a);
//2
System.out.println(reduce1.get());
}
}
8.toArray
數組操作,將數據流的元素轉換成數組。
public class ArrayTest {
@Test
public void testArray() {
List<String> list = Lists.newArrayList("a", "b", "ab");
String[] strings = list.stream().toArray(String[]::new);
//結果:a b ab
for (int i = 0; i < strings.length; i++) {
System.out.print(strings[i]+" ");
}
}
}
stream和parallelStream的區(qū)別
stream:是單管道,稱其為流,其主要用于集合的邏輯處理。
parallelStream:是多管道,提供了流的并行處理,它是Stream的另一重要特性,其底層使用Fork/Join框架實現
public class StreamTest {
@Test
public void testStream() {
List<Integer> list = Lists.newArrayList(1,2, 3,4, 5,6, 7);
//結果:1234567
list.stream().forEach(System.out::print);
}
}
public class ParallelStreamTest {
@Test
public void testParallelStream() {
List<Integer> list = Lists.newArrayList(1,2, 3,4, 5,6, 7);
//結果:5726134
list.parallelStream().forEach(System.out::print);
}
}
我們可以看到直接使用parallelStream的forEach遍歷數據,是沒有順序的。
如果要讓parallelStream遍歷時有順序怎么辦呢?
public class ParallelStreamTest {
@Test
public void testParallelStream() {
List<Integer> list = Lists.newArrayList(1,2, 3,4, 5,6, 7);
//結果:1234567
list.parallelStream().forEachOrdered(System.out::print);
}
}
parallelStream的工作原理:

實際工作中的案例
1.從兩個集合中找相同的元素。一般用于批量數據導入的場景,先查詢出數據,再批量新增或修改。
public class WorkTest {
@Test
public void testWork1() {
List<String> list1 = Lists.newArrayList("a", "b", "ab");
List<String> list2 = Lists.newArrayList("a", "c", "ab");
List<String> collect = list1.stream()
.filter(x -> list2.stream().anyMatch(e -> e.equals(x)))
.collect(Collectors.toList());
//結果:[a, ab]
System.out.println(collect);
}
}
2.有兩個集合a和b,過濾出集合a中有,但是集合b中沒有的元素。這種情況可以使用在假如指定一個id集合,根據id集合從數據庫中查詢出數據集合,再根據id集合過濾出數據集合中不存在的id,這些id就是需要新增的。
@Test
public void testWork2() {
List<String> list1 = Lists.newArrayList("a", "b", "ab");
List<String> list2 = Lists.newArrayList("a", "c", "ab");
List<String> collect = list1.stream()
.filter(x -> list2.stream().noneMatch(e -> e.equals(x)))
.collect(Collectors.toList());
//結果:[b]
System.out.println(collect);
}
3.根據條件過濾數據,并且去重做數據轉換
@AllArgsConstructor
@Data
class User {
private String name;
private Integer age;
}
@Test
public void testWork3() {
List<User> userList = new ArrayList<User>() {{
add(new User("jack",23));
add(new User("james",30));
add(new User("curry",28));
add(new User("tom",27));
add(new User("sue",29));
}};
List<String> collect = userList.stream()
.filter(x -> x.getAge() > 27)
.sorted((a, b) -> a.getAge().compareTo(b.getAge()))
.limit(2)
.map(User::getName)
.collect(Collectors.toList());
//結果:[curry, sue]
System.out.println(collect);
}
4.統(tǒng)計指定集合中,姓名相同的人中年齡最小的年齡
@Test
public void testWork4() {
List<User> userList = new ArrayList<User>() {{
add(new User("tom", 23));
add(new User("james", 30));
add(new User("james", 28));
add(new User("tom", 27));
add(new User("sue", 29));
}};
userList.stream().collect(Collectors.groupingBy(User::getName))
.forEach((name, list) -> {
User user = list.stream().sorted((a, b) -> a.getAge().compareTo(b.getAge())).findFirst().orElse(null);
//結果:name:sue,age:29
// name:tom,age:23
// name:james,age:28
System.out.println("name:" + name + ",age:" + user.getAge());
});
}
總結
到此這篇關于java8 stream常用開發(fā)技巧的文章就介紹到這了,更多相關java8 stream常用開發(fā)技巧內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
五分鐘解鎖springboot admin監(jiān)控新技巧
本文不會講如何搭建企業(yè)的運維監(jiān)控系統(tǒng),有興趣的可以去找找成熟的比如Zabbix、Prometheus,甚至比較簡單的Wgcloud都能滿足一定的需求,不在此贅述。本文講解如何使用Springboot admin對spring boot項目進行應用監(jiān)控,感興趣的朋友一起看看吧2021-06-06
mybatis-plus?執(zhí)行insert(),實體的id自動更新問題
這篇文章主要介紹了mybatis-plus?執(zhí)行insert(),實體的id自動更新問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-12-12
Java解決No enclosing instance of type PrintListFromTailToHead
這篇文章主要介紹了Java解決No enclosing instance of type PrintListFromTailToHead is accessible問題的兩種方案的相關資料,需要的朋友可以參考下2016-07-07
java中BCryptPasswordEncoder密碼的加密與驗證方式
這篇文章主要介紹了java中BCryptPasswordEncoder密碼的加密與驗證方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-08-08
mybatis.type-aliases-package的作用及用法說明
這篇文章主要介紹了mybatis.type-aliases-package的作用及用法說明,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-01-01

