JAVA8新特性之:Stream 详解

Java 8 中的 Stream 是对集合(Collection)对象功能的加强,它专一于对集合对象进行各类很是便利、高效的聚合操做(aggregate operation),或者大批量数据操做 (bulk data operation)。html

Stream表明数据流,流中的数据元素的数量多是有限的,也多是无限的。java

Stream和其它集合类的区别在于:其它集合类主要关注与有限数量的数据的访问和有效管理(增删改),而Stream并无提供访问和管理元素的方式,而是经过声明数据源的方式,利用可计算的操做在数据源上执行,固然BaseStream.iterator() 和 BaseStream.spliterator()操做提供了遍历元素的方法。编程

Java Stream提供了提供了串行和并行两种类型的流,保持一致的接口,提供函数式编程方式,以管道方式提供中间操做和最终执行操做,为Java语言的集合提供了现代语言提供的相似的高阶函数操做,简化和提升了Java集合的功能。api

Stream接口还包含几个基本类型的子接口如IntStream, LongStream 和 DoubleStream。数组

关于流和其它集合具体的区别,能够参照下面的列表:多线程

  1. 不存储数据。流是基于数据源的对象,它自己不存储数据元素,而是经过管道将数据源的元素传递给操做。
  2. 函数式编程。流的操做不会修改数据源,例如filter不会将数据源中的数据删除。
  3. 延迟操做。流的不少操做如filter,map等中间操做是延迟执行的,只有到终点操做才会将操做顺序执行。
  4. 能够解绑。对于无限数量的流,有些操做是能够在有限的时间完成的,好比limit(n) 或 findFirst(),这些操做但是实现"短路"(Short-circuiting),访问到有限的元素后就能够返回。
  5. 纯消费。流的元素只能访问一次,相似Iterator,操做没有回头路,若是你想从头从新访问流的元素,对不起,你得从新生成一个新的流。

流的操做是以管道的方式串起来的。流管道包含一个数据源,接着包含零到N个中间操做,最后以一个终点操做结束。oracle

并行 Parallelism

全部的流操做均可以串行执行或者并行执行。
除非显示地建立并行流,不然Java库中建立的都是串行流。 Collection.stream()为集合建立串行流而Collection.parallelStream()为集合建立并行流。IntStream.range(int, int)建立的是串行流。经过parallel()方法能够将串行流转换成并行流,sequential()方法将流转换成串行流。app

除非方法的Javadoc中指明了方法在并行执行的时候结果是不肯定(好比findAny、forEach),不然串行和并行执行的结果应该是同样的。dom

建立Stream

能够经过多种方式建立流:函数式编程

一、经过集合的stream()方法或者parallelStream(),好比Arrays.asList(1,2,3).stream()
二、经过Arrays.stream(Object[])方法, 好比Arrays.stream(new int[]{1,2,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)
四、BufferedReader.lines()从文件中得到行的流。
五、Files类的操做路径的方法,如listfindwalk等。
六、随机数流Random.ints()
七、其它一些类提供了建立流的方法,如BitSet.stream()Pattern.splitAsStream(java.lang.CharSequence), 和 JarFile.stream()
八、更底层的使用StreamSupport,它提供了将Spliterator转换成流的方法。

中间操做

中间操做会返回一个新的流,而且操做是延迟执行的(lazy),它不会修改原始的数据源,并且是由在终点操做开始的时候才真正开始执行。

distinct

distinct保证输出的流中包含惟一的元素,它是经过Object.equals(Object)来检查是否包含相同的元素。

下面的例子则使用 distinct 来找出不重复的单词。

List<String> l = Stream.of("a","b","c","b").distinct().collect(Collectors.toList());
System.out.println(l); 
输出结果:[a, b, c]

filter

filter返回的流中只包含知足断言(predicate)的数据。

下面的代码返回流中的偶数集合。

List<Integer> l = 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值)。

List<Integer> l = 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<List<Integer>> inputStream = Stream.of(
 Arrays.asList(1),
 Arrays.asList(2, 3),
 Arrays.asList(4, 5, 6)
 );
List<Integer> outputList = inputStream.flatMap((childList) -> childList.stream())
.collect(Collectors.toList());
输出结果:[1,2,3,4,5,6]

将最底层元素抽出来放到一块儿,最终 output 的新 Stream 里面。

limit;skip

limit 返回 Stream 的前面 n 个元素;skip 则是扔掉前 n 个元素

List<Integer> l = IntStream.range(1,100).limit(5).skip(1)
        .boxed()//IntStream转换成Stream<Integer>
        .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: three
Mapped value: THREE
Filtered value: four
Mapped value: FOUR

forEach 不能修改本身包含的本地变量值,也不能用 break/return 之类的关键字提早结束循环。

sorted

sorted()将流中的元素按照天然排序方式进行排序,若是元素没有实现Comparable,则终点操做执行时会抛出java.lang.ClassCastException异常。
sorted(Comparator<? super T> comparator)能够指定排序的方式。

对于有序流,排序是稳定的。对于非有序流,不保证排序稳定。

降序排列:
List<String> li = Stream.of("c","d", "b", "a").sorted((s1,s2) ->s2.compareTo(s1))
.collect(Collectors.toList());
System.out.println(li);
输出结果:[d, c, b, a]
升序排序:
List<String> 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,流为空时老是返回true
anyMatch只有在任意一个元素知足断言时就返回true,不然flase,
noneMatch只有在全部的元素都不知足断言时才返回true,不然flase,

System.out.println(Stream.of(1,2,3,4,5).allMatch( i -> i > 0)); 
输出结果:true
System.out.println(Stream.of(1,2,3,4,5).anyMatch( i -> i > 0)); 
输出结果:true
System.out.println(Stream.of(1,2,3,4,5).noneMatch( i -> i > 0)); 
输出结果:false
System.out.println(Stream.<Integer>empty().allMatch( i -> i > 0)); 
输出结果:true
System.out.println(Stream.<Integer>empty().anyMatch( i -> i > 0));
输出结果:false
System.out.println(Stream.<Integer>empty().noneMatch( i -> i > 0));
输出结果:true

count

count方法返回流中的元素的数量

System.out.println(Stream.of("c","d", "b", "a").count());
输出结果:4

collect

使用一个collector执行mutable reduction操做。辅助类Collectors提供了不少的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);
输出结果:
1
2
3

max、min

max返回流中的最大值,
min返回流中的最小值。

System.out.println(IntStream.range(1,10).max().getAsInt());
System.out.println(IntStream.range(1,10).min().getAsInt());
输出结果:
9
1

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);
输出结果:
15
17

toArray

将流中的元素放入到一个数组中。

并行流

像上面所说的,流操做能够是串行的,也能够是并行的。串行操做经过单线程执行,而并行操做则经过多线程执行。
下面的例子就演示了如何使用并行流进行操做来提升运行效率,代码很是简单。
首先咱们建立一个大的list,里面的元素都是惟一的:

int max = 1000000;
List<String> values = 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%。

相关文章
相关标签/搜索