你还在为大批量数据操作而发愁吗? 你还在因看到层层for循环而感到膈应吗? 你还在为写并行代码频频出bug而懊恼吗? 快去使用Java8的Stream吧.
为啥需要用Stream
- 方便, 高效的处理集合对象
- 处理过程更加清晰, 提高可读性.
- 配合Lambda表达式, 少写代码, 提高编程效率.
- 方便编写高性能的并发程序.
应用场景
Java7写法
1 | List<UserInfo> userInfos = new ArrayList<>(); |
Java8写法
1 | List<UserInfo> userInfos = new ArrayList<>(); |
简单的对比可以看出两者的不同, 当然是stream更胜一筹!!!
Stream详细介绍
Stream有三部分组成, 分别是数据源, 中间操作, 结束操作.
数据源生成
数据源生成常用的有如下方式:
- 从 Collection 数组
- Collection.stream()
- Collection.parallelStream()
- Arrays.stream(T array) 或者 Stream.of()
- 从 BufferdReader
- java.io.BufferedReader.lines()
- 静态工厂
- java.util.stream.IntStream.range()
- java.nio.file.Files.walk()
- 其他
- Random.ints()
- BitSet.stream()
- Pattern.splitAsStream(java.lang.CharSequence)
- JarFile.stream()
- 等等
中间操作
Stream的中间操作只是一个标记, 一种声明, 只有在结束操作的时候才会进行真正的计算, 中间操作有两种分类:
- 无状态:元素的处理不受前面元素的影响
- unordered()
- filter()
- map()
- mapToInt()
- mapToLong()
- mapToDouble()
- flatMap()
- flatMapToInt()
- flatMapToLong()
- flatMapToDouble()
- peek()
- 有状态:必须等到所有元素处理后才会知道最后结果
- disinct()
- sorted()
- limit()
- skip()
结束操作
结束操作也有两种分类
- 非短路操作:需要处理完全部元素才能知晓结果
- forEach()
- forEachOrdered()
- toArray()
- reduce()
- collect()
- max()
- min()
- count()
- 短路操作:不用处理全部元素就可能会返回结果
- anyMatch()
- allMatch()
- noneMatch()
- findFirst()
- findAny()
常用的api
map/flatMap
把输入流的元素, 映射成另外一种元素.
对象转字符串
1 | List<String> collect = userInfos.stream().map(userInfo -> { |
平方数
1 | List<Integer> nums = Arrays.asList(1, 2, 3, 4); |
flatMap用于把有层级的结构扁平化处理.
多List扁平化输出
1 | List<Integer> collect1 = Stream.of(Arrays.asList(1, 2, 3), Arrays.asList(4, 5, 6), Arrays.asList(7, 8, 9)) |
filter
对元素进行过滤, 通过的元素会留下来生成一个新的Stream.
过滤奇数
1 | Integer[] sixNums = {1, 2, 3, 4, 5, 6}; |
forEach/peek
遍历每个元素
遍历打印
1 | Integer[] sixNums = {1, 2, 3, 4, 5, 6}; |
forEach是结束操作, 无法重复执行, 而peek可以做到重复执行.
1 | Integer[] sixNums = {1, 2, 3, 4, 5, 6}; |
返回内容为:
1 | 1-peek1 |
findFirst
short-circuiting 操作, 总是返回第一个元素, 或者为空
1 | Optional<Integer> first = Stream.of(sixNums).findFirst(); |
它返回一个Optional对象, 使用 Optional 代码的可读性更好, 而且它提供的是编译时检查, 能极大的降低 NPE 这种 Runtime Exception 对程序的影响, 或者迫使程序员更早的在编码阶段处理空值问题, 而不是留到运行时再发现和调试.
reduce
主要是用来吧元素组合起来. 需要一个起始位置和一个组合规则, sum/min/max/average都是一种reduce.
求最大
1 | Integer[] sixNums = {1, 7, 3, 4, 5, 6}; |
limit/skip
limit 返回前n个元素; skip 扔掉前n个元素
1 | List<Integer> collect = Stream.of(sixNums).limit(1).collect(Collectors.toList()); |
注意: 放到sorted后面会让short-circuiting失效, stream会遍历完所有的元素.
sorted
对元素排序, 使用要注意顺序, 可以先进行limit/skip, 然后对较短的集合进行排序
1 | Stream.of(sixNums).limit(3).sorted(Integer::compareTo).forEach(System.out::println); |
min/max/distinct
找最小/最大/过滤重复
某个英文小说的词汇
1 | BufferedReader br = new BufferedReader(new FileReader("c:\\SUService.log")); |
Match
匹配某些元素, 有三种方式:
- allMatch 全部匹配返回true, 有一个不满足就会短路, 返回false.
- anyMatch 只要有一个则返回true, 有一个满足就会短路, 返回true.
- noneMatch 没有一个则返回true, 有一个满足就会短路, 返回false.
通过名字找人
1 | List<UserInfo> userInfos = new ArrayList<>(); |
自己生成流
Stream.generate
随机数
1 | Random seed = new Random(); |
Stream.iterate
等差数列
1 | Stream.iterate(0, n -> n + 3).limit(10) |
用 Collectors 来进行 reduction 操作
grooupingBy/partitioningBy/toMap
对数据分组
1 | // 按照名字分组 |
并行处理
stream()
串行流parallelStream()
并行流
串行流和并行流差别就是单线程和多线程的执行. 任务量小或者机器是单核的选用串行流; 任务量大且机器是多核的, 考虑使用串行流.
通过parallel()
和sequential()
能够实现并行流和串行流之间的切换.
并行流的使用需要符合两个规则
- 初始值必须为组合函数的恒定值
- 组合操作必须符合结合律