Java 8 中的 Stream 是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作(aggregate operation),或者大批量数据操作 (bulk data operation)。
Stream代表数据流,流中的数据元素的数量可能是有限的,也可能是无限的。
Stream和其它集合类的区别在于:其它集合类主要关注与有限数量的数据的访问和有效管理(增删改),而Stream并没有提供访问和管理元素的方式,而是通过声明数据源的方式,利用可计算的操作在数据源上执行,当然BaseStream.iterator()
和 BaseStream.spliterator()
操作提供了遍历元素的方法。
Java Stream提供了提供了串行和并行两种类型的流,保持一致的接口,提供函数式编程方式,以管道方式提供中间操作和最终执行操作,为Java语言的集合提供了现代语言提供的类似的高阶函数操作,简化和提高了Java集合的功能。
Stream接口还包含几个基本类型的子接口如IntStream, LongStream 和 DoubleStream。
关于流和其它集合具体的区别,可以参照下面的列表:
- 不存储数据。流是基于数据源的对象,它本身不存储数据元素,而是通过管道将数据源的元素传递给操作。
- 函数式编程。流的操作不会修改数据源,例如
filter
不会将数据源中的数据删除。 - 延迟操作。流的很多操作如filter,map等中间操作是延迟执行的,只有到终点操作才会将操作顺序执行。
- 可以解绑。对于无限数量的流,有些操作是可以在有限的时间完成的,比如
limit(n)
或findFirst()
,这些操作可是实现"短路"(Short-circuiting),访问到有限的元素后就可以返回。 - 纯消费。流的元素只能访问一次,类似Iterator,操作没有回头路,如果你想从头重新访问流的元素,对不起,你得重新生成一个新的流。
流的操作是以管道的方式串起来的。流管道包含一个数据源,接着包含零到N个中间操作,最后以一个终点操作结束。
并行 Parallelism
所有的流操作都可以串行执行或者并行执行。
除非显示地创建并行流,否则Java库中创建的都是串行流。Collection.stream()
为集合创建串行流而Collection.parallelStream()
为集合创建并行流。IntStream.range(int, int)
创建的是串行流。通过parallel()
方法可以将串行流转换成并行流,sequential()
方法将流转换成串行流。 除非方法的Javadoc中指明了方法在并行执行的时候结果是不确定(比如findAny、forEach),否则串行和并行执行的结果应该是一样的。
创建Stream
可以通过多种方式创建流:
1、通过集合的stream()
方法或者parallelStream()
,比如Arrays.asList(1,2,3).stream()
。
Arrays.stream(Object[])
方法, 比如Arrays.stream(new int[]{1,2,3})
。 3、使用流的静态方法,比如Stream.of(Object[])
, IntStream.range(int, int)
或者 Stream.iterate(Object, UnaryOperator)
,如Stream.iterate(0, n -> n * 2)
,或者generate(Supplier<T> s)
如Stream.generate(Math::random)
。 4、BufferedReader.lines()
从文件中获得行的流。 5、Files
类的操作路径的方法,如list
、find
、walk
等。 6、随机数流Random.ints()
。 7、其它一些类提供了创建流的方法,如BitSet.stream()
, Pattern.splitAsStream(java.lang.CharSequence)
, 和 JarFile.stream()
。 8、更底层的使用StreamSupport
,它提供了将Spliterator
转换成流的方法。 中间操作
中间操作会返回一个新的流,并且操作是延迟执行的(lazy),它不会修改原始的数据源,而且是由在终点操作开始的时候才真正开始执行。
distinct
distinct
保证输出的流中包含唯一的元素,它是通过Object.equals(Object)
来检查是否包含相同的元素。
下面的例子则使用 distinct 来找出不重复的单词。
Listl = Stream.of("a","b","c","b").distinct().collect(Collectors.toList());System.out.println(l); 输出结果:[a, b, c]
filter
filter
返回的流中只包含满足断言(predicate)的数据。
下面的代码返回流中的偶数集合。
Listl = IntStream.range(1,10).filter( i -> i % 2 == 0).boxed().collect(Collectors.toList());System.out.println(l); 输出结果:[2, 4, 6, 8]
map
map
方法将流中的元素映射成另外的值,新的值类型可以和原来的元素的类型不同。
作用就是把 input Stream 的每一个元素,映射成 output Stream 的另外一个元素。
下面的代码中将字符元素映射成它的哈希码(ASCII值)。
Listl = Stream.of('a','b','c').map( c -> c.hashCode()).collect(Collectors.toList());System.out.println(l); 输出结果:[97, 98, 99]
flatmap
从上面例子可以看出,map 生成的是个 1:1 映射,每个输入元素,都按照规则转换成为另外一个元素。还有一些场景,是一对多映射关系的,这时需要 flatMap。
Stream
> inputStream = Stream.of( Arrays.asList(1), Arrays.asList(2, 3), Arrays.asList(4, 5, 6) );List outputList = inputStream.flatMap((childList) -> childList.stream()).collect(Collectors.toList());输出结果:[1,2,3,4,5,6]
将最底层元素抽出来放到一起,最终 output 的新 Stream 里面。
limit;skip
limit 返回 Stream 的前面 n 个元素;skip 则是扔掉前 n 个元素
Listl = IntStream.range(1,100).limit(5).skip(1) .boxed()//IntStream转换成Stream .collect(Collectors.toList());System.out.println(l);输出结果:[2, 3, 4, 5]
peek
peek 对每个元素执行操作并返回一个新的 Stream
Stream.of("one", "two", "three", "four").filter(e -> e.length() > 3) .peek(e -> System.out.println("Filtered value: " + e)) .map(String::toUpperCase) .peek(e -> System.out.println("Mapped value: " + e)) .collect(Collectors.toList());输出结果:Filtered value: threeMapped value: THREEFiltered value: fourMapped value: FOUR
forEach 不能修改自己包含的本地变量值,也不能用 break/return 之类的关键字提前结束循环。
sorted
sorted()
将流中的元素按照自然排序方式进行排序,如果元素没有实现Comparable
,则终点操作执行时会抛出java.lang.ClassCastException
异常。
sorted(Comparator<? super T> comparator)
可以指定排序的方式。 对于有序流,排序是稳定的。对于非有序流,不保证排序稳定。
降序排列:Listli = Stream.of("c","d", "b", "a").sorted((s1,s2) ->s2.compareTo(s1)).collect(Collectors.toList());System.out.println(li);输出结果:[d, c, b, a]升序排序:List li = Stream.of("c","d", "b", "a").sorted((s1,s2) ->s1.compareTo(s2)).collect(Collectors.toList());System.out.println(li);输出结果:[a, b, c, d]
终点操作
Match
这一组方法用来检查流中的元素是否满足断言。
allMatch
只有在所有的元素都满足断言时才返回true,否则flase,流为空时总是返回trueanyMatch
只有在任意一个元素满足断言时就返回true,否则flase,noneMatch
只有在所有的元素都不满足断言时才返回true,否则flase, System.out.println(Stream.of(1,2,3,4,5).allMatch( i -> i > 0)); 输出结果:trueSystem.out.println(Stream.of(1,2,3,4,5).anyMatch( i -> i > 0)); 输出结果:trueSystem.out.println(Stream.of(1,2,3,4,5).noneMatch( i -> i > 0)); 输出结果:falseSystem.out.println(Stream.empty().allMatch( i -> i > 0)); 输出结果:trueSystem.out.println(Stream. empty().anyMatch( i -> i > 0));输出结果:falseSystem.out.println(Stream. empty().noneMatch( i -> i > 0));输出结果:true
count
count
方法返回流中的元素的数量
System.out.println(Stream.of("c","d", "b", "a").count());输出结果:4
collect
使用一个collector执行mutable reduction
操作。辅助类提供了很多的collector,java.util.stream.Collectors 类的主要作用就是辅助进行各类有用的 reduction 操作,例如转变输出为 Collection,把 Stream 元素进行归组。
System.out.println(Stream.of("c","d", "b", "a").collect(Collectors.toList()));输出结果:[c, d, b, a]
find
findAny()
返回任意一个元素,如果流为空,返回空的Optional,对于并行流来说,它只需要返回任意一个元素即可,所以性能可能要好于findFirst()
,但是有可能多次执行的时候返回的结果不一样。
findFirst()
返回第一个元素,如果流为空,返回空的Optional。 String l = Stream.of("cd","dc", "acb", "ade").filter(e -> e.contains("a")).findFirst().get();System.out.println(l);输出结果:acb
forEach、forEachOrdered
forEach
遍历流的每一个元素,执行指定的action。它是一个终点操作,和peek
方法不同。这个方法不担保按照流的encounter order
顺序执行,如果对于有序流按照它的encounter order
顺序执行,你可以使用forEachOrdered
方法。
Stream.of(1,2,3).forEach(System.out::println);输出结果:123
max、min
max
返回流中的最大值,
min
返回流中的最小值。 System.out.println(IntStream.range(1,10).max().getAsInt());System.out.println(IntStream.range(1,10).min().getAsInt());输出结果:91
reduce
这个方法的主要作用是把 Stream 元素组合起来。它提供一个起始值(种子),然后依照运算规则(BinaryOperator),和前面 Stream 的第一个、第二个、第 n 个元素组合。从这个意义上说,字符串拼接、数值的 sum、min、max、average 都是特殊的 reduce。例如 Stream 的 sum 就相当于
Integer sum = integers.reduce(0, (a, b) -> a+b); 或
Integer sum = integers.reduce(0, Integer::sum);
也有没有起始值的情况,这时会把 Stream 的前面两个元素组合起来,返回的是 Optional。
Integer total = Stream.of(2,1,3,4,5).reduce( (x, y) -> x +y).get();System.out.println(total);Integer total2 = Stream.of(2,1,3,4,5).reduce(2, (x, y) -> x +y);System.out.println(total2);输出结果:1517
toArray
将流中的元素放入到一个数组中。
并行流
像上面所说的,流操作可以是串行的,也可以是并行的。串行操作通过单线程执行,而并行操作则通过多线程执行。
下面的例子就演示了如何使用并行流进行操作来提高运行效率,代码非常简单。 首先我们创建一个大的list,里面的元素都是唯一的:int max = 1000000;Listvalues = new ArrayList<>(max);for (int i = 0; i < max; i++) { UUID uuid = UUID.randomUUID(); values.add(uuid.toString());}
现在,我们测量一下对这个集合进行排序所使用的时间。
串行排序
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
并行排序
long t0 = System.nanoTime();long count = values.parallelStream().sorted().count();System.out.println(count);long t1 = System.nanoTime();long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);System.out.println(String.format("parallel sort took: %d ms", millis));// parallel sort took: 472 ms
如你所见,所有的代码段几乎都相同,唯一的不同就是把stream()
改成了parallelStream()
, 结果并行排序快了50%。