文章目录
前言
Stream是Java8的一大亮点,是对容器对象功能的增强,它专注于对容器对象进行各种非常便利、高效的 聚合操作(aggregate operation)或者大批量数据操作!
Stream API
1.概述
Java 8扩展了集合类,可以通过 Collection.stream()
或者 Collection.parallelStream()
来创建一个Stream。
Stream 的创建需要指定一个数据源,比如 java.util.Collection
的子类,List
或者 Set
, Map
不支持。Stream 的操作可以串行执行或者并行执行。
2.Stream的特点
- Stream不是什么数据结构,它不会保存数据,只是将操作的数据结果保存到另一个对象中。
- Stream是惰性求值的(延迟执行),在中间处理的过程中,只对操作进行记录,并不会立即执行,只有等到执行终止操作的时候才会进行实际的计算,这时候中间操作才会执行。
- 可以把Stream当成一个高级版本的Iterator来使用。(原始版本的Iterator,只能一个个的遍历操作)
3.常用方法
Stream操作分类 | 作用描述 | 常用方法 |
---|---|---|
无状态 | 中间操作, 该操作不受之前元素的影响 | unordered、filter、map、mapToInt、mapToLong、mapToDouble、flatMap、flatMapToInt、flatMapToLongg、flatMapToDouble、peek |
有状态 | 中间操作,该操作只有拿到所有元素之后才能继续执行 | distinct、sorted、limit、skip |
非短路操作 | 结束操作 , 必须处理所有元素才能得到最终结果 | forEach、forEachOrdered、toArray、collect、max、min、count、reduce |
短路操作 | 结束操作 , 遇到某些符合条件的元素就可以得到最终结果 | anyMatch、allMatch、noneMatch、findFirsh、findAny |
代码如下(示例):
初始化集合
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User1 {
private String name;
}
List<String> stringList = Arrays.asList("a","b", "", "", "c", "", "a", "d","e","a");
System.out.println("字符串集合:" + stringList);
List<User1> userList = Arrays.asList(new User1("张三"),new User1("李四"),new User1("王五"));
System.out.println("用户集合:" + userList);
//输出:
字符串集合:[a, b, , , c, , a, d, e, a]
用户集合:[User1(name=张三), User1(name=李四), User1(name=王五)]
forEach
forEach()
方法用于迭代遍历每个数据
代码如下(示例):
//遍历集合
stringList.forEach(str -> System.out.print(str + " "));
stringList.forEach(System.out::print);
//输出:
a b c a d e a a
abcadea
filter
filter()
方法用于按照设置的条件过滤元素,得到满足条件的元素
代码如下(示例):
//获取stringList中非空字符串的集合
List<String> collect = stringList.stream().filter(str -> !str.isEmpty())
.collect(Collectors.toList());
System.out.println("非空字符串集合:" + collect);
//输出:非空字符串集合:[a, b, c, a, d, e, a]
count
count()
方法用于统计数量
代码如下(示例):
//统计空字符串个数
long emptyStrNum = stringList.stream().filter(str -> str.isEmpty()).count();
System.out.println("空字符串数量 = " + emptyStrNum);
//输出:空字符串数量 = 3
distinct
distinct()
方法用于去重
代码如下(示例):
//获取stringList中非空字符串的集合 去重后 转化为list集合
List<String> collect3 = stringList.stream().filter(str -> !str.isEmpty()).distinct()
.collect(Collectors.toList());
System.out.println("去重后字符串集合:" + collect3);
//提取出userList对象中的属性name并去重
List<String> nameList = userList.stream().map(User1::getName).distinct().collect(Collectors.toList());
System.out.println("nameList = " + nameList);
//输出:
去重后字符串集合:[a, b, c, d, e]
nameList = [张三, 李四, 王五]
Collectors - (Collector工具库)
Collectors 类中实现了很多的规约操作(可用于返回列表或字符串)
Collectors.toList()
Collectors.toList()
方法将Stream转化为List对象
代码如下(示例):
//查找非空、去重后通过 Collectors.toList() 转化为List列表
List<String> strList = stringList.stream().filter(str -> !str.isEmpty())
.distinct().collect(Collectors.toList());
System.out.println("strList = " + strList);
//输出:strList = [a, b, c, d, e]
Collectors.toSet()
Collectors.toSet()
方法将Stream转化为Set对象
代码如下(示例):
//查找非空后通过 Collectors.toSet() 转化为Set列表
Set<String> setList = stringList.stream().filter(str -> !str.isEmpty())
.collect(Collectors.toSet());
System.out.println("setList = " + setList);
//输出:setList = [a, b, c, d, e]
Collectors.toMap()
Collectors.toMap()
方法将Stream转化为Map对象
代码如下(示例):
Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper,BinaryOperator mergeFunction,Supplier mapSupplier)
Collectors.toMap()方法的四个参数:
- 参数1: keyMapper 用来生成key值的。
- 参数2: valueMapper 用来生成value值的。
- 参数3: mergeFunction 用在key值冲突的情况下使用(可省略),如果新元素产生的key在Map中已经出现过了,第三个参数就会定义解决的办法。
- 参数4:mapSupplier 默认返回的map类型为hashMap,可以按自己的需要自己返回不同的map实现。( 可省略 )
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User2 {
private String name;
private String age;
}
List<User2> userList1 = Arrays.asList(new User2("张三","18"),new User2("李四","19"),new User2("王五","20"));
//Collectors.toMap()方法,将List转化为Map集合
Map<String, String> map1 = userList1.stream().collect(Collectors.toMap(item -> item.getName(), item -> item.getAge()));
//map1的简写
Map<String, String> map2 = userList1.stream().collect(Collectors.toMap(User2::getName, User2::getAge));
Map<String, User2> map3 = userList1.stream().collect(Collectors.toMap(User2::getName, i -> i));
System.out.println(map1);
System.out.println(map2);
System.out.println(map3);
//输出:
{李四=19, 张三=18, 王五=20}
{李四=19, 张三=18, 王五=20}
{李四=User2(name=李四, age=19), 张三=User2(name=张三, age=18), 王五=User2(name=王五, age=20)}
注意: 如果map中出现相同的key,但是没有设置第三个参数对其进行处理,就会报错 IllegalStateException
代码如下(示例):创建一个对象集合存在相同的名字
List<User2> userList2 = Arrays.asList(new User2("张三","18"),new User2("李四","19"),new User2("张三","20"));
Map<String, String> map4 = userList2.stream().collect(Collectors.toMap(User2::getName, User2::getAge));
System.out.println(map4);
//输出:Exception in thread "main" java.lang.IllegalStateException: Duplicate key 18
解决报错:设置第三个参数,解决出现相同key时,谁覆盖谁的问题
//设置当key相同的时候,保留前面的 【如果为 (v1,v2)->v2 就是用新的覆盖旧的】
Map<String, String> map4 = userList2.stream().collect(Collectors.toMap(User2::getName, User2::getAge, (v1,v2) -> v1));
System.out.println(map4);
//输出:{李四=19, 张三=18}
统计
- 获取集合数量:
counting
代码如下(示例):
//获取集合StringList的元素数量
//Collectors.counting()
System.out.println(stringList.stream().collect(Collectors.counting()));
//简写为count()
System.out.println(stringList.stream().count());
//等价于集合的size方法
System.out.println(stringList.size());
//输出:
10
10
10
- 求平均值:
averagingInt
、averagingLong
、averagingDouble
- 求最大/最小值:
maxBy
、minBy
- 统计求和:
summingInt
、summingLong
、summingDouble
- 统计所有(包括计数、求和、最小、最大、平均):
summarizingInt
、summarizingLong
、summarizingDouble
代码如下(示例):
List<Integer> intList = Arrays.asList(1, 3, 10, 6, 8, 5, 2, 9);
// maxBy()求最大值 -- 等价于max
Optional<Integer> maxBy = intList.stream().collect(Collectors.maxBy(Integer::compareTo));
// minBy()求最大值 -- 等价于min
Optional<Integer> minBy = intList.stream().collect(Collectors.minBy(Integer::compareTo));
// averagingInt()求平均值
Double averagingInt = intList.stream().collect(Collectors.averagingInt(i -> i));
// summingInt()求和 -- 等价于mapToInt
Integer summingInt = intList.stream().collect(Collectors.summingInt(i -> i));
// summarizingInt()统计数目、求和、最小值、平均值、最大值
IntSummaryStatistics summarizingInt = intList.stream().collect(Collectors.summarizingInt(i -> i));
System.out.println("maxBy = " + maxBy);
System.out.println("minBy = " + minBy);
System.out.println("averagingInt = " + averagingInt);
System.out.println("summingInt = " + summingInt);
System.out.println("summarizingInt = " + summarizingInt);
//输出:
maxBy = Optional[10]
minBy = Optional[1]
averagingInt = 5.5
summingInt = 44
summarizingInt = IntSummaryStatistics{count=8, sum=44, min=1, average=5.500000, max=10}
分组
partitioningBy
(分区):按照条件分为两个 Map<Boolean, List>,一个是满足条件的Map和一个不满足条件的Map。groupingBy
(分组):类似于分区,但是是将集合按照条件分为多个Map,可以对进行分组之后的结果再分组。
代码如下(示例):
List<Integer> intList = Arrays.asList(1, 3, 10, 6, 8, 5, 2, 9);
//按 >5 分为两个区间
Map<Boolean, List<Integer>> partitioningBy = intList.stream().collect(Collectors.partitioningBy(i -> i > 5));
//按 >5 分为两组
Map<Boolean, List<Integer>> groupingBy = intList.stream().collect(Collectors.groupingBy(i -> i > 5));
//先按 >5 分为两组,然后再在前面分组满足条件的基础上对(满足条件的集合)再对 >8 进行分组
Map<Boolean, Map<Boolean, List<Integer>>> groupingBy2 = intList.stream().collect(Collectors.groupingBy(i -> i > 5, Collectors.groupingBy(i -> i > 8)));
System.out.println("分区 = " + partitioningBy);
System.out.println("分组 = " + groupingBy);
System.out.println("两次分组 = " + groupingBy2);
//输出:
分区 = {false=[1, 3, 5, 2], true=[10, 6, 8, 9]}
分组 = {false=[1, 3, 5, 2], true=[10, 6, 8, 9]}
两次分组 = {false={false=[1, 3, 5, 2]}, true={false=[6, 8], true=[10, 9]}}
joining
joining()
:可以将stream中的元素用特定的连接符(没有的话,则直接连接)连接成一个新的字符串。
代码如下(示例):
List<String> stringList2 = Arrays.asList("a","b", " ", " ", "c", " ", "a", "d","e","a");
String joining = stringList2.stream().collect(Collectors.joining("-"));
System.out.println("joining = " + joining);
//输出:joining = a-b- - -c- -a-d-e-a
reducing
reducing()
方法利用Comparator(比较器)和BinaryOperator(二元运算符)进行减少流中的元素。规约
reducing(U identity, Function<? super T, ? extends U> mapper, BinaryOperator<U> op)
参数: BinaryOperator: 这是一个函数式接口,是给两个相同类型的量,返回一个跟这两个量相同类型的一个结果,伪表达式为 (T,T) -> T。默认给了两个实现
maxBy
和minBy
,根据比较器来比较大小并分别返回最大值或者最小值。
使用场景: Collectors.reducing 主要用于取List列表中的某一类型中的最大或最小的一个元素。
注意: 如果最值相同,取列表顺序最先出现的一个。
代码如下(示例):
List<Integer> intList2 = Arrays.asList(1, 2, 3, 4);
//根据比较器来比较大小并分别返回最大值或者最小值
Integer reducing = intList2.stream().collect(Collectors.reducing(100, i -> i, (v1, v2) -> (v1 + v2 - 1))); // 100 + 10 - 4
System.out.println("reducing = " + reducing);
Integer reducing1 = intList2.stream().collect(Collectors.reducing(100, i -> i, (v1, v2) -> (v1 + v2 + 1)));// 100 + 10 + 4
System.out.println("reducing1 = " + reducing1);
Integer reducing2 = intList2.stream().collect(Collectors.reducing(100, i -> i, (v1, v2) -> (v1 + v2))); // 100 + 10
System.out.println("reducing2 = " + reducing2);
Optional<Integer> reduce1 = Optional.ofNullable(intList2.stream().reduce(100, Integer::sum));
System.out.println("reduce1 " + reduce1.get());
Optional<Integer> reduce2 = Optional.ofNullable(intList2.stream().reduce(100, Integer::sum, (v1, v2) -> (v1 + v2 - 1)));
System.out.println("reduce2 = " + reduce2.get());
//输出:
reducing = 102
reducing1 = 114
reducing2 = 110
reduce1 110
reduce2 = 110
map
map()
方法用于映射每个元素到对应的结果,该函数会被应用到每个元素上,并将其映射成一个新的元素。
代码如下(示例):
//使用map输出stringList中元素对应的两倍并去重
List<String> doubleStr = stringList.stream().map(str -> str += str).distinct()
.collect(Collectors.toList());
System.out.print(doubleStr + " ");
//通过map获取userList列表中的User对象的name属性组成一个list列表
List<String> nameList1 = userList.stream().map(user -> user.getName()).distinct()
.collect(Collectors.toList());
System.out.println(nameList1);
//输出:[aa, bb, , cc, dd, ee] [张三, 李四, 王五]
flatMap
flatMap()
方法用于 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。
代码如下(示例):
//flagMap转换为流后再转化为list列表输出
List<String> stringList1 = stringList.stream().flatMap(s -> {
//将每个元素按照分隔符,转换成一个stream
String[] split = s.split(",");
return Arrays.stream(split);
}).collect(Collectors.toList());
System.out.println(stringList1);
//输出:[a, b, , , c, , a, d, e, a]
limit
limit(n)
方法用于获取指定数量 (n个) 的流。
代码如下(示例):
Random random = new Random();
//通过 limit 输出3个随机数
random.ints().limit(3).forEach(r -> System.out.print(r + " "));
System.out.println();
// 三个参数:random.ints(生成数量,最小值,最大值)
//生成10个1-100之间的随机数,再通过limit取出5个输出
random.ints(10,1,100).limit(5).forEach(r -> System.out.print(r + " "));
System.out.println();
//输出:
1667244870 1205442857 1105903851
14 75 24 55 2
skip
skip(n)
方法用于跳过 (n个) 元素,配合 limit(n) 可实现分页。
代码如下(示例):
List<Integer> numbers = Arrays.asList(0, 9 ,6 , 5, 6, 3, 2, 1, 12, 5, 8, 3, 9, 3);
//过滤得到集合中>1的元素,然后去重,跳过前2个,然后取出5个
List<Integer> integerList = numbers.stream().filter(i -> i > 1)
.distinct()
.skip(2)
.limit(5).collect(Collectors.toList());
System.out.println(integerList);
//输出:[5, 3, 2, 12, 8]
sorted
sorted()
方法用于对流进行排序,这是自然排序,流中元素需实现Comparable
接口。
代码如下(示例):
//两个参数:random.ints(最小值,最大值)
//随机生成0-1000的随机数,通过limit取8个,然后排序输出
random.ints(0,1000).limit(8).sorted().forEach(i -> System.out.print(i + " "));
System.out.println();
//输出:157 392 588 590 816 819 829 883
统计结果收集器
- 获取元素数量:
getCount
- 最大值/最小值:
getMax
/getMin
- 总和:
getSum
- 平均值:
getAverage
用于统计结果的收集器,主要用于int、double、long
等基本类型上。
代码如下(示例):
List<Integer> numbers = Arrays.asList(0, 9 ,6 , 5, 6, 3, 2, 1, 12, 5, 8, 3, 9, 3);
//通过mapToInt转化
IntSummaryStatistics intStatus = numbers.stream().mapToInt((i) -> i).summaryStatistics();
System.out.println("列表中元素数量:" + intStatus.getCount());
System.out.println("列表中最大数 : " + intStatus.getMax());
System.out.println("列表中最小数 : " + intStatus.getMin());
System.out.println("所有数之和 : " + intStatus.getSum());
System.out.println("平均值 : " + intStatus.getAverage());
//输出:
列表中元素数量:14
列表中最大数 : 12
列表中最小数 : 0
所有数之和 : 72
平均值 : 5.142857142857143
流的终止操作
方法名称 | 描述 |
---|---|
count | 返回流中元素的总个数 |
max | 返回流中元素最大值 |
min | 返回流中元素最小值 |
findFirst | 返回流中第一个元素 |
findAny | 返回流中第一个元素(随机) |
allMatch | 接收一个 Predicate 函数,当流中每个元素都符合该断言时才返回true,否则返回false |
noneMatch | 接收一个 Predicate 函数,当流中每个元素都不符合该断言时才返回true,否则返回false |
anyMatch | 接收一个 Predicate 函数,只要流中有一个元素满足该断言则返回true,否则返回false |
代码如下(示例):
System.out.println("列表中的元素的总个数:" + intList.stream().count());
System.out.println("列表中的元素最大值:" + intList.stream().max(Integer::compareTo).get());
System.out.println("列表中的元素最小值:" + intList.stream().min(Integer::compareTo).get());
System.out.println("列表中的第一个元素:" + intList.stream().findFirst().get());
System.out.println("列表中的第一个元素(随机):" + intList.stream().findAny().get());
System.out.println("列表中的元素是否都>=1:" + intList.stream().allMatch(i -> i >= 1));
System.out.println("列表中的元素是否都不>1:" + intList.stream().noneMatch(i -> i > 1));
System.out.println("列表中的元素是否有一个>5:" + intList.stream().anyMatch(i -> i > 5));
//输出:
列表中的元素的总个数:8
列表中的元素最大值:10
列表中的元素最小值:1
列表中的第一个元素:1
列表中的第一个元素(随机):1
列表中的元素是否都>=1:true
列表中的元素是否都不>1:false
列表中的元素是否有一个>5:true
创建流的两种方式
stream
:为集合创建串行流parallelStream
:为集合创建并行流
代码如下(示例):
List<Integer> intList1 = Arrays.asList(1, 2, 3, 4, 5, 6);
System.out.print("stream串行:");
intList1.stream().forEach(System.out::print);
System.out.print("\nparallelStream并行:");
intList.parallelStream().forEach(System.out::print);
System.out.println();
//输出:
stream串行:123456
parallelStream并行:582961031
System.out.println("parallelStream并行forEach输出:");
intList1.parallelStream().forEach(i -> System.out.println("线程 = " + Thread.currentThread().getName() +" : 输出 = " + i));
System.out.println("parallelStream并行使用forEachOrdered顺序输出:");
intList1.parallelStream().forEachOrdered(i -> System.out.println("线程 = " + Thread.currentThread().getName() +" : 输出 = " + i));
//输出:
parallelStream并行forEach输出:
线程 = main : 输出 = 4
线程 = ForkJoinPool.commonPool-worker-4 : 输出 = 1
线程 = ForkJoinPool.commonPool-worker-13 : 输出 = 6
线程 = ForkJoinPool.commonPool-worker-9 : 输出 = 3
线程 = ForkJoinPool.commonPool-worker-2 : 输出 = 5
线程 = ForkJoinPool.commonPool-worker-6 : 输出 = 2
parallelStream并行使用forEachOrdered顺序输出:
线程 = ForkJoinPool.commonPool-worker-2 : 输出 = 1
线程 = ForkJoinPool.commonPool-worker-6 : 输出 = 2
线程 = ForkJoinPool.commonPool-worker-6 : 输出 = 3
线程 = ForkJoinPool.commonPool-worker-6 : 输出 = 4
线程 = ForkJoinPool.commonPool-worker-6 : 输出 = 5
线程 = ForkJoinPool.commonPool-worker-6 : 输出 = 6
小结:
parallelStream
是利用多线程并行执行的,通过parallelStream
可以很大程度简化我们使用并发操作。- 使用
parallelStream
是平行处理的,所以顺序每次都不一定一致,如果想要顺序是按照原来Stream
的数据一样顺序输出,可以通过forEachOrdered
方法实现。- 所以调用
forEachOrdered
方法顺序执行的话,就不是多线程并行处理了,是一个线程进行处理。