Stream 作为 Java 8 的一大亮点,它与 java.io 包里的 InputStream 和 OutputStream 是完全不同的概念。它也不同于 StAX 对 XML 解析的 Stream,也不是 Amazon Kinesis 对大数据实时处理的 Stream。Java 8 中的 Stream 是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作(aggregate operation),或者大批量数据操作 (bulk data operation)。Stream API 借助于同样新出现的 Lambda 表达式,极大的提高编程效率和程序可读性。同时它提供串行和并行两种模式进行汇聚操作,并发模式能够充分利用多核处理器的优势,使用 fork/join 并行方式来拆分任务和加速处理过程。通常编写并行代码很难而且容易出错, 但使用 Stream API 无需编写一行多线程的代码,就可以很方便地写出高性能的并发程序。所以说,Java 8 中首次出现的 java.util.stream 是一个函数式语言+多核时代综合影响的产物。
https://www.ibm.com/developerworks/cn/java/j-lo-java8streamapi/
以上是网络上对于Java 8 Stream Api意义的解释。简单来说,Stream Api 给了一种比较方便合适的方式来做聚合操作。 在 Java 中,聚合操作往往只能通过数据库和普通循环操作来实现,这种方法写起来笨拙而且不够优雅。 例如在 Java 8 之前,我们找出指定部门的员工,然后将工号以排序输出到另一个 List 中,就只能这么写:
1 | List<Employee> employeeList = new ArrayList<>(); |
但是在 Java 8 中,我们有了更简洁,更高效的方式,因为 Stream Api 还有并发的特性。
1 | List<Integer> employeeIds = AllEmployeeList.parallelStream() |
什么是流
Stream 不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的 Iterator。原始版本的 Iterator,用户只能显式地一个一个遍历元素并对其执行某些操作;高级版本的 Stream,用户只要给出需要对其包含的元素执行什么操作,比如 “过滤掉长度大于 10 的字符串”、”获取每个字符串的首字母”等,Stream 会隐式地在内部进行遍历,做出相应的数据转换。
Stream 就如同一个迭代器(Iterator),单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从面前流过,一去不复返。
而和迭代器又不同的是,Stream 可以并行化操作,迭代器只能命令式地、串行化操作。顾名思义,当使用串行方式去遍历时,每个 item 读完后再读下一个 item。而使用并行去遍历时,数据会被分成多个段,其中每一个都在不同的线程中处理,然后将结果一起输出。Stream 的并行操作依赖于 Java7 中引入的 Fork/Join 框架(JSR166y)来拆分任务和加速处理过程。
流的操作
生成流
从集合创建流
- stream() − 为集合创建串行流。
- parallelStream() − 为集合创建并行流。
- Stream.of(Collections) - 创建串行流
其他构建方法 - java.io.BufferedReader.lines() - 从 BufferedReader 构建流
- java.util.stream.IntStream.range() - 指定 int 范围内循环的串行流
- java.nio.file.Files.walk() - 指定路径遍历文件流
- java.util.Spliterator
- Random.ints()
- BitSet.stream()
- Pattern.splitAsStream(java.lang.CharSequence)
- JarFile.stream()
流的方法
forEach
迭代每个流的元素的方法,如果对效率有要求可以在并行流的基础上进行遍历,此时元素顺序将无法保证按照原有顺序
1 | allEmployeeList.parallelStream().forEach(System.out::println); |
map
映射、转换每个元素的方法。具体来说,是将原流的每个元素进行转换,合成一个新的流的方法。
1 | //映射所有部门 |
filter
按照条件过滤元素,经过过滤的元素生成一个新 Stream
1 |
|
limit / skip
限制流输出的元素个数,取流前 n 个元素生成新流
1 | List<Integer> numbersOnLimit = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); |
skip 方法与 limit 相反,将跳过前 n 个元素生成新流
1 | List<Integer> numbersOnSkip = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); |
还有一个需要注意的是由于 Stream 是管道形式的,在排序后执行 limit/skip 方法并不能起到减少排序的次数的作用
sorted
对流进行排序生成新流,
1 | //对对象的排序,分别演示了排列 int 类型和 String 类型的操作 |
min / max
在流中取最大或最小的方法,复杂度为 O(n),所以效率是相对排序要高很多。
1 | Employee maxSn = allEmployeeList.stream().max(Comparator.comparingInt(Employee::getSn)).get(); |
get
从具有唯一值的流中取出一个值,通常 min / max / limit 等操作后获取一个值/对象的方法
orElse
讲了get,就必须要知道 orElse,因为过滤操作结束后流中并不一定是永远有值的,所以我们会在orElse中赋予一个默认值进行后续程序操作。
1 | int orElseTest = numbers.stream().filter(i -> i > 10).max(Comparator.naturalOrder()).orElse(-1); |
allMatch / anyMatch / noneMatch
- allMatch:Stream 中全部元素符合条件,返回 true
- anyMatch:Stream 中只要有任意元素符合条件,返回 true
- noneMatch:Stream 中没有一个元素符合条件,返回 true 这个效率也会很高,因为在判断时候不需要遍历所有的元素,比如 allMatch 在有一个元素不符合条件时将直接返回
1 | boolean anyMatch = numbers.stream().anyMatch(i -> i > 5); |
reduce
reduce 可以有两个参数,可以说 sum, min, max, average 等都是特殊的reduce
1 | // 字符串连接,concat = "ABCD" |