来一起学习下 Java 8 的 Stream 流

继上一篇文章 来系统学习下 lambda 表达式吧 - 掘金 (juejin.cn),今天我准备学习一下 Java 8 中的 Stream 流了。

为什么相关的优秀文章那么多,我却还要自己写一遍呐?写来写去东西都差不多。其实我只是想记录下来我所学习的东西,全当是笔记了。写下来肯定要记忆更深刻一点,并且方便以后复习。

当然,文章发布出来能够帮助到大家一点那就更好了。我只能尽量避免文章中出现错误。话不多说,开始今天的学习。

简介

Java 8 中的 Stream 流和 Java IO 中的各种流没有任何关系。

Java8 中的 Stream 不存储数据,它通过函数式编程模式来对集合进行链状流式操作。

Stream 的操作大体上分为两种:中间操作终止操作

  1. 中间操作:可以有多个,每次返回一个新的流(Stream),可进行链式操作。

  2. 终端操作:只能有一个,每次执行完,这个流也就处理结束了,无法执行下一个操作,因此只能放在最后。

举个例子:

int[] arr = {1, 2, 3, 4, 5, 6};
Arrays.stream(arr).filter(i -> i > 3).count();
复制代码

意思就是返回数组 arr 中值大于 3 的元素数量。

其中,count() 就是一个终端操作filter() 就是一个中间操作filter() 还是返回的新的流。

IntStream intStream = Arrays.stream(arr).filter(i -> i > 3);
复制代码

创建 Stream

创建 Stream 流的方式有多种:数组、集合、数字 Stream、自己创建,直接看下面代码你就明白了。

// 集合调用 stream() 方法获取流
List<String> list = new ArrayList<>();
list.add("1");
Stream<String> stream = list.stream();

// 数组 Arrays.stream() 获取流
IntStream stream = Arrays.stream(new int[]{1, 2, 3, 4, 5});

// Stream.of() 方法获取流
IntStream intStream = IntStream.of(1, 2, 4);
DoubleStream doubleStream = DoubleStream.of(1, 2, 4);

// Stream.generate() 创建流
Random random = new Random();
Supplier<Integer> supplier = () -> random.nextInt(100);
Stream<Integer> limit = Stream.generate(supplier).limit(5);
复制代码

操作 Stream

Stream 的一系列操作必须要使用终止操作,否者整个数据流是不会流动起来的,即处理操作不会执行。

操作流的方法有很多,有中间操作终端操作

其中中间操作又可以分为两大类:无状态操作、有状态操作。

  • map 或者 filter 会从输入流中获取每一个元素,并且在输出流中得到一个结果,这些操作没有内部状态,称为无状态操作。
  • reduce、sum、max 这些操作都需要内部状态来累计计算结果,所以称为有状态操作。

以上这些概念性的东西看看就好,还是挑一些常用的方法直接上代码看看 Stream 流的具体操作吧。

filter

filter 顾名思义,就是过滤、筛选的意思。它是一个中间操作,返回的是一个新的 Stream 。

filter 操作会对一个 Stream 中的所有元素一一进行判断,不满足条件的就被过滤掉了,剩下的满足条件的元素就构成了一个新的 Stream。

List<String> list = new ArrayList<>();
list.add("小黑");
list.add("小胖");
list.add("小六");
list.add("一鑫");
Stream<String> stream = list.stream().filter(item -> item.contains("小"));
stream.forEach(System.out::println);
复制代码

这里我们使用 filter 操作过滤出了包含“小”字的名字。

结果如下:

小黑
小胖
小六

filter() 方法接收的是一个 Predicate(Java 8 新增的一个函数式接口,接受一个输入参数返回一个布尔值结果)类型的参数,关于 Predicate、Supplier、Consumer 等等这些 JDK 新增的函数式接口可以另写一篇文章来分析,这里就不讲述了。

map

Stream.map()Stream 中最常用的一个转换方法,可以把一个 Stream 对象转为另外一个 Stream 对象。

List<String> list = new ArrayList<>();
list.add("小黑");
list.add("小胖");
list.add("小六");
list.add("一鑫");
Stream<String> stream = list.stream().map(item -> "二班" + item);
stream.forEach(System.out::println);
复制代码

这里我们使用 map 操作给各项加上了前缀“二班”。

结果如下:

二班小黑
二班小胖
二班小六
二班一鑫

map() 方法接收的是一个 Function(Java 8 新增的一个函数式接口,接受一个输入参数 T,返回一个结果 R)类型的参数。

再举个例子,把字符串数组转为数字:

String[] arr = {"1", "2", "3"};
Stream<String> s1 = Arrays.stream(arr);
Stream<Integer> s2 = s1.map(i -> Integer.valueOf(i));
s2.forEach(System.out::println);
复制代码

这其中的 i -> Integer.valueOf(i) 就是一个 Function ,就相当于这样:

Function<String, Integer> fun = i -> Integer.valueOf(i);
Stream<Integer> s2 = s1.map(fun);
复制代码

输入参数是 String ,返回 Integer。

匹配

有如下 3 个匹配的方法:

  • anyMatch(),只要有一个元素匹配传入的条件,就返回 true。

  • allMatch(),只有有一个元素不匹配传入的条件,就返回 false;如果全部匹配,则返回 true。

  • noneMatch(),只要有一个元素匹配传入的条件,就返回 false;如果全部匹配,则返回 true。

List<String> list = new ArrayList<>();
list.add("小黑");
list.add("小胖");
list.add("小六");
list.add("一鑫");

boolean anyMatchFlag = list.stream().anyMatch(item -> item.contains("小"));
boolean allMatchFlag = list.stream().allMatch(item -> item.length() > 1);
boolean noneMatchFlag = list.stream().noneMatch(item -> item.startsWith("小"));
System.out.println(anyMatchFlag);
System.out.println(allMatchFlag);
System.out.println(noneMatchFlag);
复制代码

结果如下:

true
true
false

组合

reduce 是 Stream 的一个聚合方法,它可以把一个 Stream 的所有元素按照聚合函数聚合成一个结果。reduce 方法传入的对象是BinaryOperator 接口,它定义了一个 apply 方法,负责把上次累加的结果和本次的元素进行运算,并返回累加的结果。

举个例子,数组求和:

Optional<Integer> optional = Stream.of(1, 2, 3, 4, 5).reduce((a, b) -> a + b);
System.out.println(optional);
System.out.println(optional.orElse(-1));
复制代码

结果如下:

Optional[15]
15

Optional类主要解决的问题是臭名昭著的空指针异常(NullPointerException),这里的 orElse 方法意思是在对象为空的时候返回默认值 -1 。

还有一种情况:

Integer reduce = Stream.of(1, 2, 3, 4, 5).reduce(6, (a, b) -> a + b);
System.out.println(reduce);
复制代码

有起始值,有运算规则,两个参数,此时返回的类型和起始值类型一致。

结果如下:

21

其他

上面列出了部分常用操作,其实还有很多没有列出来:

flatMap、peek、distinct、sorted、limit、skip、count、max、min、findFirst、findAny、forEach

也不在一个个的讲解了,直接上几个案例一看估计聪明的你就明白了。

System.out.println("======去重(distinct)======");
IntStream.of(2,3,4,5,6,2,4).distinct().forEach(System.out::println);

System.out.println("======去重+排序(orted)======");
IntStream.of(8,3,4,3,6,2,6).distinct().sorted().forEach(System.out::println);

System.out.println("======跳过+限制(skip + limit)======");
Arrays.asList("A", "B", "C", "D", "E", "F").stream().skip(2).limit(3).forEach(System.out::println);

System.out.println("======统计个数(count)======");
System.out.println(Stream.of(1, 2, 3, 4).count());

System.out.println("======统计最小值(min)======");
Stream<Integer> s = Stream.of(1, 2, 3, 4);
Optional<Integer> min = s.min(Comparator.comparingInt(i -> i));
System.out.println(min.get());

System.out.println("======查找第一个元素(findFirst)======");
Optional<Integer> first = Stream.of(1, 2, 3, 4).findFirst();
System.out.println(first.get());
复制代码

结果如下:

image.png

转换 Stream

集合或数组既可以转成流,相应的我们也可以使用 collect、toArray 方法将流转回去。

List<String> list = new ArrayList<>();
list.add("小黑");
list.add("小胖");
list.add("小六");
list.add("一鑫");

String[] strArray = list.stream().toArray(String[]::new);
System.out.println(Arrays.toString(strArray));

List<String> list1 = list.stream().map(item -> "二班" + item).collect(Collectors.toList());
List<String> list2 = list.stream().collect(Collectors.toCollection(ArrayList::new));
System.out.println(list1);
System.out.println(list2);

String str = list.stream().collect(Collectors.joining("|"));
System.out.println(str);
复制代码

结果如下:

[小黑, 小胖, 小六, 一鑫]
[二班小黑, 二班小胖, 二班小六, 二班一鑫]
[小黑, 小胖, 小六, 一鑫]
小黑|小胖|小六|一鑫

Collectors 是一个收集器的工具类,内置了一系列收集器实现,比如说 toList() 方法将元素收集到一个新的 java.util.List 中;toCollection() 方法将元素收集到一个新的 java.util.ArrayList 中;joining() 方法将元素收集到一个可以用分隔符指定的字符串中等。

总结

通常情况下,对 Stream 的元素进行处理是单线程的,即一个一个元素进行处理。有时候我们希望可以并行处理 Stream 元素,因为在元素数量非常大的情况,并行处理可以大大加快处理速度。

把一个普通 Stream 转换为可以并行处理的 Stream 非常简单,只需要使用 parallel 方法进行转换。

暂时写到这里,后续还有待完善。

猜你喜欢

转载自juejin.im/post/7039581261089751048