Stream 流解读

时间:2022-07-23
本文章向大家介绍Stream 流解读,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

java.util.Stream 可以对元素列表进行一次或多次操作。Stream操作可以是中间值也可以是最终结果。最后的操作返回的是某种类型结果,而中间操作返回的是stream本身。因此你可以在一行代码链接多个方法调用。Streams被创建于java.util.Collection ,比如 list or set (map 并不支持)。Stream可以顺序执行,也可以并行执行。

•中间操作:filter、map、mapToInt、mapToLong、mapToDouble、flatMap、sorted、distinct、limit、skip、of、iterate•终止操作:forEach、count、collect、reduce、toArray、anyMatch、allMatch、noneMatch、findAny、findFirst、max、min•原始类型特化流:IntStream、LongStream、DoubleStream

过滤 Filter

Filter通过predicate判断函数来过滤所有的元素。这个操作是中间操作,需要通过终止操作才会触发执行。

代码:com.winterbe.java8.samples.stream.Stream_filterstringCollection    .stream()    .filter((s) -> s.startsWith("a"))    .forEach(System.out::println);
// "aaa2", "aaa1"

映射 Map

map是一种中间过程操作,借助函数表达式将元素转换成另一种形式。下面的例子将每个字符串转换成大写的字符串。但你也可以使用map将每个对象转换为另一种类型。最终输出的结果类型依赖于你传入的函数表达式。

stringCollection    .stream()    .map(String::toUpperCase)    .sorted((a, b) -> b.compareTo(a))  //由大到小    .forEach(System.out::println);
// "DDD2", "DDD1", "CCC", "BBB3", "BBB2", "AAA2", "AAA1"

映射 flatMap

如果涉及到一对多映射,需要将映射结果放入Stream中。使用flatMap方法的效果是,转换后的多个结果并不是分别映射成一个流,而是映射成流的内容。

代码:com.winterbe.java8.samples.stream.Stream_flatMap
List<String> lists = Arrays.asList("Hello", "World");lists.stream().flatMap(word-> Stream.of(word.split("")))        .distinct()        .forEach(System.out::println);

排序 Sorted

Sorted是一个中间态操作,它返回流的有序视图。除非你传递自定义的Comparator,否则元素按默认的由小到大排序。

代码:com.winterbe.java8.samples.stream.Stream_sorted
//默认排序stringCollection.stream().sorted().forEach(System.out::println);System.out.println(stringCollection);特别注意:`sorted`只是创建流的排序视图,并没有改变原始集合的顺序。所以说`stringCollection`的顺序并没有改变。
//自定义排序,按字符串,由大到小stringCollection        .stream()        .map(String::toUpperCase)        .sorted((a, b) -> b.compareTo(a))        .forEach(System.out::println);

归约 Reduce

终止型操作,通过给定的函数表达式来处理流中的前后两个元素,或者中间结果与下一个元素。Lambda 反复结合每一个元素,直到流被归约成一个值。例如求和或查找最大元素。

代码:com.winterbe.java8.samples.stream.Stream_reduce
// 将流数据列表拆分多批,sum初始为0,每批都执行 (sum, p) -> sum = sum + p.age,得到局部的sum总和。并行计算思想// 最后通过 (sum1, sum2) -> sum1 + sum2 ,计算最终的总和// (sum1, sum2) -> sum1 + sum2,主要适用于并行,parallelStream(),单线程是无效的。
private static void test3(List<Person> persons) {    Integer ageSum = persons.parallelStream().reduce(0, (sum, p) -> sum += p.age, (sum1, sum2) -> sum1 + sum2);    System.out.println(ageSum);}

更多reduce用法可参考:https://blog.csdn.net/io_field/article/details/54971679

计数 Count

Count是一个终止型操作,返回一个long类型的元素列表总数。

代码:com.winterbe.java8.samples.stream.Stream_countlong startsWithB =    stringCollection        .stream()        .filter((s) -> s.startsWith("b"))        .count();
System.out.println(startsWithB);    // 3

匹配 Match

各种匹配操作用于判断是否满足stream条件。所有的操作都完成后,返回一个boolean类型结果。

代码:com.winterbe.java8.samples.stream.Stream_matchList<String> stringCollection = new ArrayList<>();stringCollection.add("ddd2");stringCollection.add("aaa2");stringCollection.add("bbb1");stringCollection.add("aaa1");stringCollection.add("bbb3");stringCollection.add("ccc");stringCollection.add("bbb2");stringCollection.add("ddd1");
// 只需要一个条件满足boolean anyStartsWithA = stringCollection.stream().anyMatch((s) -> s.startsWith("a"));System.out.println("anyMatch:" + anyStartsWithA); // true
// 所有条件都要满足boolean allStartsWithA = stringCollection.stream().allMatch((s) -> s.startsWith("a"));System.out.println("allMatch:" + allStartsWithA); // false
// 所有的条件都要不满足boolean noneStartsWithZ = stringCollection.stream().noneMatch((s) -> s.startsWith("z"));System.out.println("noneMatch:" + noneStartsWithZ); // true
// 返回任意一个元素Optional<String> anyE = stringCollection.stream().findAny();System.out.println("findAny:" + anyE.get());
//返回第一个元素Optional<String> firstE = stringCollection.stream().findFirst();System.out.println("findFirst:" + firstE.get());

跳过 skip

返回一个扔掉前n个元素的流

代码:com.winterbe.java8.samples.stream.Stream_skip// 扔掉前三个元素stringCollection    .stream()    .skip(3)    .forEach(System.out::println);

输出 limit

只取前N个结果

代码:com.winterbe.java8.samples.stream.Stream_limit// 取前三个元素stringCollection    .stream()    .limit(3)    .forEach(System.out::println);

输出 collect

接受各种做法作为参数,将流中的元素累积成一个汇总结果

常见例子:

•对一个交易列表按货币分组,获得该货币的所有交易额总和(返回一个Map<Currency,Integer>)•将交易列表分成两组,贵的和不贵的(返回一个Map<Boolean,List<Transaction>>)•创建多级分组,比如按城市对交易分组,然后进一步按照贵的或不贵分组

Collectors常见方法:

•Collectors.toList,得到List列表•Collectors.toSet,得到Set集合•Collectors.joining ,通过连接符拼接字符串•Collectors.groupingBy(Function<? super T,? extends K>) ,按K值分组,返回Map<K,List>•Collectors.groupingBy(Function<? super T,? extends K>, Collector<? super T,A,D>),二级分组,得到两级Map•Collectors.partitioningBy(Predicate<? super T> predicate) ,分区是分组的特殊情况,返回一个布尔值,意味着得到的分组Map的key只能是Boolean,于是它最多可以分为两组•Collectors.maxBy,求最大值,需要传一个自定义的Comparator•Collectors.reducing,广义的归约汇总。

代码:com.winterbe.java8.samples.stream.Stream_collect
// 将字符串换成大写,并用逗号连接起来List<String> citys = Arrays.asList("USA", "Japan", "France");String cityS = citys.stream().map(x -> x.toUpperCase()).collect(Collectors.joining(", "));
// 按性别分组Map<String, List<Student>> maps = studentList.stream().collect(Collectors.groupingBy(Student::getSex));
// 先按性别分组,然后再按年龄段分组Map<String, Map<String, List<Student>>> maps = studentList.stream()   .collect(Collectors.groupingBy(Student::getSex,      Collectors.groupingBy(s -> {          if (s.getAge() < 20) {              return "低age";          } else {              return "高age";          }      })));
// 按年龄25分成两个组Map<Boolean, List<Student>> maps = studentList.stream().collect(Collectors.partitioningBy(s -> {    if (s.getAge() < 25) {        return true;    } else {        return false;    }}));
// 找出年龄最大的人Optional<Student> optional1 = studentList.stream().collect(Collectors.maxBy(Comparator.comparing(Student::getAge)));optional1.ifPresent(System.out::println);
// 年龄总和// reducing的参数,第一个:初始值。第二个:转换函数。第三个:累积函数int sum = studentList.stream().collect(Collectors.reducing(0, Student::getAge, Integer::sum));

并行 Streams

如下所述,流可以是串行执行,也可以并行执行。对于流的串行执行是单个线程完成。而并行流处理则是在多个线程上同时执行。

下面这个例子将会演示如何通过并行流处理来显著提升性能。

首先我们创建一个大容量的List元素集合:

代码:com.winterbe.java8.samples.stream.Stream_reduce
int max = 1000000;List<String> values = new ArrayList<>(max);for (int i = 0; i < max; i++) {    UUID uuid = UUID.randomUUID();    values.add(uuid.toString());}

现在我们测量对此集合的流排序所花费的时间。

串行 Sort

long t0 = System.nanoTime();
long count = values.stream().sorted().count();System.out.println(count);
long t1 = System.nanoTime();
long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);System.out.println(String.format("sequential sort took: %d ms", millis));
// sequential sort took: 899 ms

代码地址:https://github.com/aalansehaiyang/java8-tutorial