1.流的概述
a sequence of elements from source that supports aggregate operations
- sequence of elements: 一个流对外提供一个接口,可以访问到一串特定的数据。流不存储元素,但是可以根据需要进行计算转化
- source:数据来源,如数据结构,数组,文件等
- aggregate operation:聚合操作,流支持像 SQL 操作或者其他函数式语言的操作,如 filter/map/reduce/find/match/sorted 等
- pipelining: 很多流操作也是返回一个流
- Internal Iteration: 流操作进行迭代,用户感知不到循环遍历
Stream 语法
- 类似 SQL 语句,遵循“做什么而非怎么做”原则
流的工作流程
- 流的创建
- 流的转换,将流转换为其他流的中间操作,可包括多个步骤(惰性操作)
- 流的计算结果。这个操作会强制执行之前的惰性操作。这个步骤以后,流就再也不用了
2.流的创建
- Collection 接口的 stream 方法
1
2Stream<String> a1 = new ArrayList<String>().stream();
Stream<String> a2 = new HashSet<String>().stream();- 还有其他的子类,如 LinkedList, LinkedSet, TreeSet, Vector 等
- Arrays.stream 可以将数组转为 Stream
1
Stream<String> b1 = Arrays.stream("a,b,c,d,e".split(","), 3, 5);
- 利用 Stream 类进行转化
- of 方法,直接将数组转化
1
2
3Stream<Integer> c1 = Stream.of(new Integer[5]);
Stream<String> c2 = Stream.of("a,b,c".split(","));
Stream<String> c3 = Stream.of("a", "b", "c");- empty 方法,产生一个空流
1
Stream<String> d1 = Stream.empty();
- 利用 Stream 类进行转化
- generate 方法,接收一个 Lambda 表达式
1
2Stream<String> e1 = Stream.generate(()->"hello");
Stream<Double> e2 = Stream.generate(Math::random);- iterate 方法,接收一个种子,和一个 Lambda 表达式
1
Stream<BigInteger> e3 = Stream.iterate(BigInteger.ZERO, n -> n.add(BigInteger.ONE));
- 其他类/方法产生 Stream 流
- Files.lines 方法
1
Stream<String> contents = Files.lines(Paths.get("C:/abc.txt"));
- Pattern 的 splitAsStream 方法
1
Stream<String> words = Pattern.compile(",").splitAsStream("a,b,c");
基本类型流
- IntStream,LongStream,DoubleStream
1 | IntStream s1 = IntStream.of(1,2,3,4,5); |
并行流
- 使得所有的中间转换操作都将被并行化
Collections.parallelStream()将任何集合转为并行流Stream.parallel()方法,产生一个并行流
1 | IntStream s1 = IntStream.range(1,10000000); |
- 注意:需要保证传给并行流的操作不存在竞争
3.Stream 的转换
过滤 filter
filter(Predicate\<? super T> predicate)- 接收一个 Lambda 表达式,对每个元素进行判定,符合条件留下
1 | Stream<Integer> s1 = Stream.of(1, 2, 3, 4, 5); |
去重 distinct
distinct()- 对流的元素进行过滤,去除重复,只留下不重复的元素
1 | // 基本类型包装类对象去重 |
排序 sorted
sorted()- 对流的基本类型包装类元素进行排序
1 | Stream<Integer> s1 = Stream.of(3,2,4,1,5); |
- 提供 Comparator,对流的元素进行排序
1 | String[] planets = new String[] { |
- 对流的自定义对象元素进行排序,调用对象的 compareTo 方法
1 | ArrayList<Cat> cats = new ArrayList<>(); |
转化 map
- 利用方法引用对流每个元素进行函数计算
1 | Stream<Double> s1 = Stream.of(-1.5, 2.5, -3.5); |
- 利用 Lambda 表达式对流每个元素进行函数计算
1 | Stream<Integer> s3 = Stream.of(1,2,3,4,5); |
- 利用方法引用,对流每个元素进行函数计算返回 Stream
1 | String[] planets = new String[] { |
- 利用方法引用,对流每个元素进行函数计算返回 Stream,并合并
1 | Stream<Stream<String>> allLetters = Stream.of(planets).map(word -> letters(word)); |
抽取 limit
1 | // 获取前n个元素 |
跳过 skip
1 | // 跳过前n个元素 |
连接 concat
1 | // 连接两个流 |
额外调试 peek
1 | Stream<Double> s1 = Stream.iterate(1.0, n -> n * 2) |
4.Optional 类型
- 一个包装器对象
- 要么包装了类型 T 的对象,要么没有包装任何对象(还是 null)
- 如果 T 有值,那么直接返回 T 的对象
- 如果 T 是 null,那么可以返回一个替代物
1 | Optional<String> s1 = Optional.of(new String("abc")); |
Optional<T>创建
- of 方法
- empty 方法
- ofNullable 方法,对于对象有可能为 null 情况下,安全创建
1 | Optional<String> s5 = Optional.of(new String("abc")); |
Optional<T>使用
- get 方法,获取值,不安全的用法
-orElse 方法,获取值,如果为 null,采用替代物的值 - orElseGet 方法,获取值,如果为 null,采用 Lambda 表达式值返回
- orElseThrow 方法,获取值,如果为 null,抛出异常
- ifPresent 方法,判断是否为空,不为空返回 true
- isPresent(Consumer), 判断是否为空,如果不为空,则进行后续 Consumer 操作,如果为空,则不做任何事情
- map(Function), 将值传递给 Function 函数进行计算。如果为空,则不计算
Optional<T>注意事项
- 直接使用 get,很容易引发 NoSuchElementException 异常
- 使用 isPresent 判断值是否存在,这和判断 null 是一样的低效
5.流的计算结果
简单约简(聚合函数)
- count(), 计数
1 | Stream<Integer> s1 = Stream.of(a); |
- max(Comparator),最大值,需要比较器
1 | Stream<Integer> s2 = Stream.of(a); |
- min(Comparator),最小值,需要比较器
1 | Stream<Integer> s3 = Stream.of(a); |
- findFirst(), 找到第一个元素
1 | Stream<Integer> s4 = Stream.of(a); |
- findAny(), 找到任意一个元素
1 | Stream<Integer> s5 = Stream.of(a); |
- anyMatch(Predicate),如有任意一个元素满足 Predicate,返回 true
1 | Stream<Integer> s6 = Stream.of(a); |
- allMatch(Predicate),如所有元素满足 Predicate,返回 true
1 | Stream<Integer> s7 = Stream.of(a); |
- noneMatch(Predicate),如没有任何元素满足 Predicate,返回 true
1 | Stream<Integer> s8 = Stream.of(a); |
自定义约简
- reduce,传递一个二元函数 BinaryOperator,对流元素进行计算
- 如求和、求积、字符串连接等
1 | Integer[] a = new Integer[] {2,4,6,8}; |
查看/遍历元素
- iterator(),遍历元素
- forEach(Consumer),应用一个函数到每个元素上
1 | Integer[] a = new Integer[] {2,4,6,8}; |
存放到数据结构中
- toArray(),将结果转为数组
- collect(Collectors.toList()),将结果转为 List
1 | // List |
- collect(Collectors.toSet()),将结果转为 Set
1 | Stream<Integer> s3 = Stream.of(a); |
- collect(Collectors.toMap()), 将结果转为 Map
1 | List<Person> persons = new ArrayList<Person>(); |
- collect(Collectors.joining()), 将结果连接起来
1 | // 将流变换为字符流,并连接起来 |
流的高阶计算
- 分组 groupingBy 和分区 partitionBy
- 分组后的约简
- counting
- summing
- maxBy
- minBy
- 以上方法均在 java.util.stream.Collectors 中
6.流的应用
Java Stream 的优点
- 统一转换元素
- 过滤元素
- 利用单个操作合并元素
- 将元素序列存放到某一个集合中
- 搜索满足某些条件的元素的序列
- 类似 SQL 操作,遵循“做什么而非怎么做”原则
- 简化了串行/并行的大批量操作
Java Stream vs 循环迭代代码
- Stream 广泛使用 Lambda 表达式,只能读取外围的 final 或者 effectively final 变量,循环迭代代码可以读取/修改任意的局部变量。
- 在循环迭代代码块中,可以随意 break/continue/return,或者抛出异常,而 Lambda 表达式无法完成这些事情。
- Stream 流不是淘汰循环迭代代码,应该是两者相互搭配使用
Stream 应用注意事项
- 一个流,一次只能一个用途,不能多个用途,用了不能再用
- 避免创建无限流
- 注意操作顺序
- 谨慎使用并行流
- 底层使用 Fork-Join Pool,处理计算密集型任务
- 数据量过小不用
- 数据结构不容易分解的时候不用,如 LinkedList 等 –数据频繁拆箱装箱不用
- 涉及 findFirst 或者 limit 的时候不用
Stream vs Collection
- Stream 和 Collection 两者可以互相转化
- 如果数据可能无限,用 Stream
- 如果数据很大很大,用 Stream
- 如果调用者将使用查找/过滤/聚合等操作,用 Stream
- 当调用者使用过程中,发生数据改变,而调用者需要对数据一致性有较高要求,用 Collection
Leave a comment