Stream API
文章目录
什么是 Stream 流
Stream将要处理的元素集合看作一种流 ,流是从支持数据处理操作的源生成的元素序列,源可以是数组、文件、集合、函数。流不是集合元素,它不是数据结构并不保存数据,它的主要目的在于计算
特性:
- 无存储,为函数式编程而生。stream不存储数据,而是按照特定的规则对数据进行计算,一般会输出结果
- 对于stream 的修改都不会修改背后的数据源,比如对stream 执行过滤操作并不会删除被过滤的元素,而是会产生一个不包含被过滤元素的新stream
- 惰性执行。stream 上的操作并不会立即执行,只有等到用户真正需要结果的时候才会执行;只有调用终端操作时,中间操作才会执行
- 可消费性。 stream 只能被“消费”一次,一旦遍历过就会失效,就像容器的迭代器,想要再次遍历必须重新生成
stream 流的生成方式
通过集合生成流
List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);
Stream<Integer> stream = integerList.stream();
List<Test> testList = new LinkedList<>();
testList.add(new Test("1", "邓闻", 18698151330L, "120104299103024011"));
testList.add(new Test("2", "小明", 19842020131L, "1020420112191402511"));
testList.add(new Test("3", "小强", 19842190221L, "10231450112191402511"));
Stream<Test> testStream = testList.stream();
通过数组生成流
- 通过
Arrays.stream()
方法生成流,并且该方法生成的流是数值流IntStream
而不是Stream<Integer>
int[] intArr = new int[]{1, 2, 3, 4, 5};
IntStream stream = Arrays.stream(intArr);
- 使用数值流可以避免计算过程中拆箱装箱,提高性能
- Stream API 提供了 mapToInt,MapToDouble,mapTOLong 三种方式将对象流 stream 转换成对应的数值流,同时提供了boxed 方法将数值流转化为对象流
// 获取long 类型 电话号的值
LongStream result = testList.stream().map(Test::getTelphone).mapToLong(Long::longValue);
long[] telphones = result.toArray();
System.out.println(Arrays.toString(telphones));
// 字符串string 转int类型,获取id 的值
int[] ids = testList.stream().map(Test::getId).mapToInt(Integer::valueOf).toArray();
System.out.println(Arrays.toString(ids));
通过值生成流
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
可以通过empty 方法生成一个空流
Stream<Integer> stream = Stream.empty();
通过文件生成流
通过Files.lines 方法得到一个流,并且得到的每个流是给定文件中的一行
Stream<String> lines = Files.lines(Paths.get("data.txt"), Charset.defaultCharset())
通过函数生成
Stream API 提供了iterator 和 generate 两个静态方法从函数中生成流
- iterator 方法接收两个参数,第一个为初始化值,第二个为进行的函数操作,因为iterator 生成的流为无限流,通过limit方法对流进行了截断,只生成5个偶数
Stream<Integer> stream = Stream.iterate(0, n -> n + 2).limit(5);
- generator 方法接收一个参数,方法参数类型为Supplier,由它为流提供值。generate生成的流也是无限流,因此通过limit对流进行了截断
Stream<Double> stream = Stream.generate(Math::random).limit(5);
流的操作类型
- 中间操作: 一个流可以后面跟随零个或多个中间操作。其目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。这类操作都是惰性化的,仅仅调用到这类方法,并没有真正的开始流的遍历,真正的遍历需等到终端操作时。
- 无状态: 指元素的处理不受之前元素的影响
- 有状态: 指该操作只有拿到所有元素之后才能继续下去
- 终端操作: 一个流有且只能有一个终端操作,当这个操作执行后,流就被关闭了,无法再被操作,因此一个流只能被遍历一次,若想在遍历需要通过源数据在生成流。终端操作的执行,才会真正开始流的遍历
- 非短路操作: 指必须处理所有元素才能得到最终结果
- 短路操作: 指遇到某些符合条件的元素就可以得到结果 ,如A||B ,只要A 为true,则无需判断B的结果
stream 操作类型分类 | ||
---|---|---|
中间操作 (Intermediate operations) | 无状态 | unordered(); filter(); map(); mapToInt(); mapToLong(); mapToDouble(); flatMap(); flatMapToLong(); flatMapToDouble(); flatMapToInt(); peek(); |
有状态 | distinct(); sorted(); limit(); skip(); | |
终端操作 (Terminal operations) | 非短路操作 | forEach();forEachOrdered(); toArray();reduce();collect(); max();min();count(); |
短路操作 | anyMatch();allMatch();noneMatch();findFirst();findAny() |
所有中间操作
方法 | 说明 |
---|---|
sequential | 返回一个相等的串行的Stream对象,如果原Stream对象已经是串行就可能会返回原对象 |
parallel | 返回一个相等的并行的Stream对象,如果原Stream对象已经是并行的就会返回原对象 |
unordered | 返回一个不关心顺序的Stream对象,如果原对象已经是这类型的对象就会返回原对象 |
onClose | 返回一个相等的Steam对象,同时新的Stream对象在执行Close方法时会调用传入的Runnable对象 |
close | 关闭Stream对象 |
filter | 元素过滤:对Stream对象按指定的Predicate进行过滤,返回的Stream对象中仅包含未被过滤的元素 |
map | 元素一对一转换:使用传入的Function对象对Stream中的所有元素进行处理,返回的Stream对象中的元素为原元素处理后的结果 |
mapToInt | 元素一对一转换:将原Stream中的使用传入的IntFunction加工后返回一个IntStream对象 |
flatMap | 元素一对多转换:对原Stream中的所有元素进行操作,每个元素会有一个或者多个结果,然后将返回的所有元素组合成一个统一的Stream并返回; |
distinct | 去重:返回一个去重后的Stream对象 |
sorted | 排序:返回排序后的Stream对象 |
peek | 使用传入的Consumer对象对所有元素进行消费后,返回一个新的包含所有原来元素的Stream对象 |
limit | 获取有限个元素组成新的Stream对象返回 |
skip | 抛弃前指定个元素后使用剩下的元素组成新的Stream返回 |
takeWhile | 如果Stream是有序的(Ordered),那么返回最长命中序列(符合传入的Predicate的最长命中序列)组成的Stream;如果是无序的,那么返回的是所有符合传入的Predicate的元素序列组成的Stream。 |
dropWhile | 与takeWhile相反,如果是有序的,返回除最长命中序列外的所有元素组成的Stream;如果是无序的,返回所有未命中的元素组成的Stream。 |
所有终端操作
方法 | 说明 |
---|---|
iterator | 返回Stream中所有对象的迭代器; |
spliterator | 返回对所有对象进行的spliterator对象 |
forEach | 对所有元素进行迭代处理,无返回值 |
forEachOrdered | 按Stream的Encounter所决定的序列进行迭代处理,无返回值 |
toArray | 返回所有元素的数组 |
reduce | 使用一个初始化的值,与Stream中的元素一一做传入的二合运算后返回最终的值。每与一个元素做运算后的结果,再与下一个元素做运算。它不保证会按序列执行整个过程。 |
collect | 根据传入参数做相关汇聚计算 |
min | 返回所有元素中最小值的Optional对象;如果Stream中无任何元素,那么返回的Optional对象为Empty |
max | 与Min相反 |
count | 所有元素个数 |
anyMatch | 只要其中有一个元素满足传入的Predicate时返回True,否则返回False |
allMatch | 所有元素均满足传入的Predicate时返回True,否则False |
noneMatch | 所有元素均不满足传入的Predicate时返回True,否则False |
findFirst | 返回第一个元素的Optioanl对象;如果无元素返回的是空的Optional; 如果Stream是无序的,那么任何元素都可能被返回。 |
findAny | 返回任意一个元素的Optional对象,如果无元素返回的是空的Optioanl。 |
isParallel | 判断是否当前Stream对象是并行的 |
串行/并行流
parallelStream()
返回一个相等的并行的Stream对象,如果原Stream对象已经是并行的就会返回原对象
- parallelStream() 支持并行执行,提高程序运行效率。但是线程不安全,如果使用不当可能会发生线程安全问题
- parallelStream() 每次执行的结果都不相同,与多线程程序中执行的结果类似。如果希望最后顺序是按照原来Stream 的数据顺序,那可以调用forEachOrdered()方法
- parallelStream() 使用的线程池是 ForkJoinPool.common ,可以通过设置
-Djava.util.concurrent.ForkJoinPool.common.parallelism = N
来调整线程池的大小 - parallelStream() 默认的线程数量是计算机处理器的数量
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
numbers.parallelStream().forEach(System.out::println);
numbers.parallelStream().forEachOrdered(System.out::println);
unordered()
在并行流中 使用 unordered()方法,使流变得无序,提高并行效率
Arrays.asList("1", "2", "3", "4", "5")
.parallelStream()
.unordered()
.limit(2).forEach(System.out::print);
串行流转并行流
除了直接创建并行流的方式,还可以通过parallel()把顺序流转换成并行流:
Optional<Integer> findFirst = list.stream().parallel().filter(x->x>6).findFirst();
并行流转串行流
使用 sequential() 方法返回一个相等的串行的Stream对象,如果原Stream对象已经是串行就可能会返回原对象
Arrays.asList("1", "2", "3").parallelStream().sequential();
filter() 筛选
stream 使用 filter() 进行筛选
通过使用filter() 进行条件筛选,filter 的方法参数为一个条件
List<Integer> integerList = Arrays.asList(1, 1, 2, 3, 4, 5);
Stream<Integer> stream = integerList.stream().filter(i -> i > 3);
distinct() 去重
stream 使用 distinct() 快速去除重复的元素
List<Integer> integerList = Arrays.asList(1, 1, 2, 3, 4, 5);
Stream<Integer> stream = integerList.stream().distinct();
limit() 返回指定个数
stream 使用 limit() 方法指定返回流的个数,limit 的参数值必须 >= 0,否则将会抛出异常
List<Integer> integerList = Arrays.asList(1, 1, 2, 3, 4, 5);
Stream<Integer> stream = integerList.stream().limit(3);
skip () 跳过流中的元素
stream 使用 skip() 方法跳过流中的元素,skip 的参数值必须 >= 0,否则将会抛出异常
List<Integer> integerList = Arrays.asList(1, 1, 2, 3, 4, 5);
Stream<Integer> stream = integerList.stream().skip(2);
通过skip() 和 limit() 方法 的结合可实现类似于分页的操作
// 第一页,20个对象
Integer pageNum = 1;
Integer pageSize = 20;
testList.stream().skip((pageNum - 1) * pageSize ).limit(pageSize);
对象转换
map() 流映射
所谓流映射就是将接收的元素映射成另外一个元素
把数组流中的每一个值,使用所提供的函数执行一遍,得到元素个数相同的数组流
// 从数据集中 获取电话号码列表
List<Long> telphones = testList.stream().map(Test::getTelphone).collect(Collectors.toList());
// 通过map() 方法转为另一个List 对象输出
String[] testArray = ids.split(",");
List<TestMark> list = Arrays.stream(testArray)
.map(id -> {
TestMark testMark = new TestMark();
testMark.setAlarmId(Integer.valueOf(id));
return testMark;
}).collect(Collectors.toList());
flatMap() 流转换
stream 通过 flatMap() ,对流进行扁平化处理,将一个流中的每个值都转换为另一个流
flatMap 操作具有对该流的元素应用一对多变换的效果,然后将所得到的元素展平到新的流中
扁平化的理解: flat 是扁平的意思,它把数组流中的每一个值,使用所提供的函数执行一遍,一一对应,得到元素相同的数组流。只不过里面的元素也可能是一个子数组流。flatMap的扁平化意味着把这些子树组合并到一个数组以后,形成一个一级结构的数组,这个数组的元素个数大概率会和原数组流的个数不同。
flatMap 的应用一般是先map 再flatMap,先将每个元素做处理,然后将处理结果flat 平铺合并,返回一个完整的一级数据结构。
String[] words = new String[]{"Hello","World"};
List<String> a = Arrays.stream(words)
.map(word -> word.split(""))
.flatMap(Arrays::stream)
.distinct()
.collect(toList());
a.forEach(System.out::print);
flatMap 和 map 的区别
map 是对每一个元素进行处理,flatmap 是对每一个元素进行处理后,如果这个元素的大小是 1 ,那么就和map操作一样;如果处理后是个List或者Array类型,那么flatmap 就会将这个List或者Array的每个元素变成一个新元素,放到集合里
元素匹配
allMatch() 匹配所有
List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);
if (integerList.stream().allMatch(i -> i > 3)) {
System.out.println("值都大于3");
}
anyMatch() 匹配其中一个
List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);
if (integerList.stream().anyMatch(i -> i > 3)) {
System.out.println("存在大于3的值");
}
noneMatch() 全部不匹配
List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);
if (integerList.stream().noneMatch(i -> i > 3)) {
System.out.println("值都小于3");
}
数学计算
统计
- 通过 count() 方法统计
List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);
Long result = integerList.stream().count();
- 通过counting
List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);
Long result = integerList.stream().collect(counting());
最大值/最小值获取
- 通过 min() / max() 方法获取最大值,最小值
Optional<Integer> min = menu.stream().map(Dish::getCalories).min(Integer::compareTo);
Optional<Integer> max = menu.stream().map(Dish::getCalories).max(Integer::compareTo);
OptionalInt min = menu.stream().mapToInt(Dish::getCalories).min();
OptionalInt max = menu.stream().mapToInt(Dish::getCalories).max();
- 通过minBy /maxBy 方法获取最大值,最小值
Optional<Integer> min = menu.stream().map(Dish::getCalories).collect(minBy(Integer::compareTo));
Optional<Integer> max = menu.stream().map(Dish::getCalories).collect(maxBy(Integer::compareTo));
- 通过reduce 方法获取最大值,最小值
Optional<Integer> min = menu.stream().map(Dish::getCalories).reduce(Integer::min);
Optional<Integer> max = menu.stream().map(Dish::getCalories).reduce(Integer::max);
求和
- 通过 sum() 方法求和
IntStream intStream = IntStream.of(50, 100, 150, 200, 250, 300);
int sumVal = intStream.sum();
testList.stream().mapToLong(Test::getTelphone).sum();
- 通过 Collectors.summing 方法求和
- summingLong 方法
- summingInt 方法
- summingDouble 方法
testList.stream().collect(Collectors.summingLong(Test::getTelphone));
- 通过 reduce() 方法求和
testList.stream().mapToLong(Test::getTelphone).reduce(0,Long::sum);
reduce() 方法将流中的元素组合起来
Java 1.8 新特性——Stream 流中 Reduce 操作
查找
findFirst() 查找第一个
List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> result = integerList.stream().filter(i -> i > 3).findFirst();
findAny() 随机查找一个
List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> result = integerList.stream().filter(i -> i > 3).findAny();
通过findAny 方法找到其中一个大于三的元素并打印,因为内部进行优化的原因,当找到第一个满足大于三的元素时就结束,该方法结果和findFirst 方法结果一样。提供findAny 方法是为了更好的利用并行流,FindFirst方法在并行上限制更多
sorted() 排序
正序排序
stream 使用 sorted() 进行排序
list.stream()
.sorted(Comparator.comparing(list::getSignInTime)
倒序排序
使用 reversed()函数实现倒叙排序
list.stream()
.sorted(Comparator.comparing(list::getSignInTime).reversed()
包含Null 值处理的排序
stream 的 sorted 排序字段是不能为null 的,否则会抛出 NullPointException 错误
- null 值对象放前面: 可使用
Comparator.nullsFirst(String::compareTo)
list.stream()
.sorted(Comparator.comparing(list::getSignInTime,Comparator.nullsFirst(String::compareTo)))
- null 值对象放后面: 可使用
Comparator.nullsLast(Integer::compareTo)
list.stream()
.sorted(Comparator.comparing(list::getSignInTime,Comparator.nullsLast(String::compareTo)))
多个字段排序
使用 thenComparing()
方法实现多个字段的排序
list.stream()
.sorted(Comparator.comparing(list::getSignInTime)
.thenComparing(list::getId));
collect() 收集数据
- Collectors.toList(); 转换成List 集合
- Collectors.toCollection(ArrayList::new) 收集流中的数据(ArrayList)到指定的集合中
- Collectors.toSet(); 转换成set集合
- Collectors.toCollection(TreeSet::new); 转换成特定的set集合
- Collectors.toMap(x->x ,x-> x + 1) 转换成map
- Collectors.minBy(Integer::compare) 求最小值
- Collectors.maxBy(Integer::compare) 求最大值
- Collectors.summingInt(x -> x) 求和
- Collectors.averagingInt() 平均值
- Collectors.counting() 总个数
- Collectors.summarizingDouble(x -> x ) 可以获取最大值,最小值,平均值,总和值,总数
DoubleSummaryStatistics s = Stream.of(1,3,4).collect(Collectors.summarizingDouble(x->x));
s.getAverage();
s.getCount();
s.getMax();
s.getSum();
s.getMin();
- Collectors.GroupingBy(x -> x) 分组操作
Map<Integer,List<Integer>> map = Stream.of(1,3,3,4).collect(Collectors.groupingBy(x ->x));
Map<Object, Long> maps = Stream.of(1,3,3,4).collect(Collectors.groupingBy(x ->x, Collectors.counting()));
HashMap<Integer,Long> hashMap = Stream.of(1,3,3,4).collect(Collectors.groupingBy(x ->x,HashMap::new,Collectors.counting()));
- 多字段分组
testList.stream().collect(Collectors.groupingBy(Test::getId,Collectors.groupingBy(Test::getTelphone,Collectors.groupingBy(Test::getIdNumber))));
- Collectors.partitioningBy(x->x > 2) 分区操作
- 把数据分成两部分 key为 true/false,一个true 列表,一个false 列表
// 满足条件得放在true 列表, 不满足条件的放在false 列表
Map<Boolean,List<Integer>> collect5 = Stream.of(1,3,4).collect(Collectors.partitioningBy(x -> x > 2));
Map<Boolean,Long> collect4 = Stream.of(1,3,4).collect(Collectors.partitioningBy(x-> x > 2,Collectors.counting()));
- Collectors.joining() 拼接字符串
- joining() 无参数–等价于 joining(“”)
- joining(CharSequence delimiter) 一个参数
- joining(CharSequence delimiter, CharSequence prefix,CharSequence suffix) 三个参数(前缀+后缀)
Stream.of("a","b","c").collect(Collectors.joining("."));
Stream<Student> studentStream = Stream.of(
new Student("赵丽颖", 52, 95),
new Student("杨颖", 56, 88),
new Student("迪丽热巴", 56, 55),
new Student("柳岩", 52, 33)
);
//拼接操作
// 无参:join()
String joinStr1 = studentStream.map(s -> s.getName()).collect(Collectors.joining());
System.out.println(joinStr1);
// 一个参数:joining(CharSequence delimiter)
String joinStr2 = studentStream.map(s -> s.getName()).collect(Collectors.joining(","));
System.out.println(joinStr2);
// 三个参数:joining(CharSequence delimiter, CharSequence prefix,CharSequence suffix)
String joinStr3 = studentStream.map(s -> s.getName()).collect(Collectors.joining("—","^_^",">_<"));
System.out.println(joinStr3);
-
Collectors.reducing(0,x -> x + 1,(x,y) -> x + y) 在累计求值得时候,还可以对参数值进行改变,这里是都+1再求和。
-
Collectors.collcetingAndThen(Collectors.joining(“.”),x-> x + “d”); 先执行collect操作后再执行第二个参数的表达式。这里是先拼接字符串,再在最后拼接"d"。
-
Collectors.mapping(…) 跟map操作类似,只是参数有点区别
Stream.of("a","b","c").collect(Collectors.mapping(x->x.toUpperCase(),Collectors.joining(".")));
参考文献
Java8的Stream()与ParallelStream()的区别说明
map 和 flatmap 的区别