Stream是 Java 8新增长的类,用来补充集合类。java
Stream表明数据流,流中的数据元素的数量多是有限的,也多是无限的。git
Stream和其它集合类的区别在于:其它集合类主要关注与有限数量的数据的访问和有效管理(增删改),而Stream并没有提供访问和管理元素的方式,而是经过声明数据源的方式,利用可计算的操做在数据源上执行,固然BaseStream.iterator() 和 BaseStream.spliterator()操做提供了遍历元素的方法。github
Java Stream提供了提供了串行和并行两种类型的流,保持一致的接口,提供函数式编程方式,以管道方式提供中间操做和最终执行操做,为Java语言的集合提供了现代语言提供的相似的高阶函数操做,简化和提升了Java集合的功能。编程
本文首先介绍Java Stream的特色,而后按照功能分类逐个介绍流的中间操做和终点操做,最后会介绍第三方为Java Stream作的扩展。数组
Stream接口还包含几个基本类型的子接口如IntStream, LongStream 和 DoubleStream。安全
关于流和其它集合具体的区别,能够参照下面的列表:网络
流的操做是以管道的方式串起来的。流管道包含一个数据源,接着包含零到N个中间操做,最后以一个终点操做结束。并发
全部的流操做均可以串行执行或者并行执行。除非显示地建立并行流,不然Java库中建立的都是串行流。 Collection.stream()
为集合建立串行流而Collection.parallelStream()
为集合建立并行流。IntStream.range(int, int)
建立的是串行流。经过parallel()
方法能够将串行流转换成并行流,sequential()
方法将流转换成串行流。app
通常,除非方法的Javadoc中指明了方法在并行执行的时候结果是不肯定(好比findAny、forEach),不然串行和并行执行的结果应该是同样的。less
流能够从非线程安全的集合中建立,当流的管道执行的时候,非concurrent数据源不该该被改变。下面的代码会抛出java.util.ConcurrentModificationException
异常:
List<String> l = new ArrayList(Arrays.asList("one", "two")); Stream<String> sl = l.stream(); sl.forEach(s -> l.add("three"));
在设置中间操做的时候,能够更改数据源,只有在执行终点操做的时候,才有可能出现并发问题(抛出异常,或者不指望的结果),好比下面的代码不会抛出异常(而且结果中包含新加入的数据):
List<String> l = new ArrayList(Arrays.asList("one", "two")); Stream<String> sl = l.stream(); l.add("three"); sl.forEach(System.out::println);
此处的
中间操
做指的是在流操做开始前的操做。 流对象对数据源的操做在一条语句中操做完成,完成后可转为结果或者转为其余流的数据源,可是该流对象再继续操做(至关于一个流对象开始有数据源,后来数据流转给别的流,本身没有数据源,不能操做原数据源了)。
对于concurrent数据源,不会有这样的问题,好比下面的代码很正常:
List<String> l = new CopyOnWriteArrayList<>(Arrays.asList("one", "two")); Stream<String> sl = l.stream(); sl.forEach(s -> l.add("three"));
虽然咱们上面例子是在终点操做中对非并发数据源进行修改,可是非并发数据源也可能在其它线程中修改,一样会有并发问题。
大部分流的操做的参数都是函数式接口,可使用Lambda表达式实现。它们用来描述用户的行为,称之为行为参数(behavioral parameters)。
若是这些行为参数有状态,则流的操做的结果多是不肯定的,好比下面的代码:
List<String> l = new ArrayList(Arrays.asList("one", "two", ……)); class State { boolean s; } final State state = new State(); Stream<String> sl = l.stream().map(e -> { if (state.s) return "OK"; else { state.s = true; return e; } }); sl.forEach(System.out::println);
上面的代码在并行执行时屡次的执行结果多是不一样的。这是由于这个lambda表达式是有状态的。
有反作用的行为参数是被鼓励使用的。
反作用指的是行为参数在执行的时候有输入输入,好比网络输入输出等。
行为参数指的是stream的操做函数中的lambda参数,例如
filter
方法中的参数。
这是由于Java不保证这些反作用对其它线程可见,也不保证相同流管道上的一样的元素的不一样的操做运行在同一个线程中。
不少有反作用的行为参数能够被转换成无反作用的实现。通常来讲println()这样的反作用代码不会有害。
ArrayList<String> results = new ArrayList<>(); stream.filter(s -> pattern.matcher(s).matches()) .forEach(s -> results.add(s)); // 反作用代码
上面的代码能够改为无反作用的。
List<String>results = stream.filter(s -> pattern.matcher(s).matches()) .collect(Collectors.toList()); // No side-effects!
某些流的返回的元素是有肯定顺序的,咱们称之为 encounter order
。这个顺序是流提供它的元素的顺序,好比数组的encounter order是它的元素的排序顺序,List是它的迭代顺序(iteration order),对于HashSet,它自己就没有encounter order。
一个流是不是encounter order主要依赖数据源和它的中间操做,好比数据源List和Array上建立的流是有序的(ordered),可是在HashSet建立的流不是有序的。
sorted()
方法能够将流转换成有序的,unordered()
能够将流转换成无序的。 除此以外,一个操做可能会影响流的有序,好比map方法,它会用不一样的值甚至类型替换流中的元素,因此输入元素的有序性已经变得没有意义了,可是对于filter方法来讲,它只是丢弃掉一些值而已,输入元素的有序性仍是保障的。
对于串行流,流有序与否不会影响其性能,只是会影响肯定性(determinism),无序流在屡次执行的时候结果多是不同的。
对于并行流,去掉有序这个约束可能会提供性能,好比distinct、groupingBy这些聚合操做。
一个操做或者函数op知足结合性意味着它知足下面的条件:
(a op b) op c == a op (b op c)
对于并发流来讲,若是操做知足结合性,咱们就能够并行计算:
a op b op c op d == (a op b) op (c op d)
好比min、max以及字符串链接都是知足结合性的。
能够经过多种方式建立流:
一、经过集合的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类的操做路径的方法,如list、find、walk等。 六、随机数流Random.ints()
。 七、其它一些类提供了建立流的方法,如BitSet.stream()
, Pattern.splitAsStream(java.lang.CharSequence)
, 和 JarFile.stream()
。 八、更底层的使用StreamSupport
,它提供了将Spliterator
转换成流的方法。
中间操做会返回一个新的流,而且操做是延迟执行的(lazy),它不会修改原始的数据源,并且是由在终点操做开始的时候才真正开始执行。 Java的这种设计会减小中间对象的生成。
下面介绍流的这些中间操做:
distinct保证输出的流中包含惟一的元素,它是经过Object.equals(Object)来检查是否包含相同的元素。
List<String> l = Stream.of("a","b","c","b") .distinct() .collect(Collectors.toList()); System.out.println(l); //[a, b, c]
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方法将流中的元素映射成另外的值,新的值类型能够和原来的元素的类型不一样。
下面的代码中将字符元素映射成它的哈希码(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 + flattern的功能,它将映射后的流的元素所有放入到一个新的流中。它的方法定义以下:
<R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper)
能够看到mapper函数会将每个元素转换成一个流对象,而flatMap方法返回的流是将原流中元素的每个子元素组合成的流对象。
下面这个例子中将一首诗生成一个按行分割的流,而后在这个流上调用flatmap获得单词的小写形式的集合,去掉重复的单词而后打印出来。
String poetry = "Where, before me, are the ages that have gone?\n" + "And where, behind me, are the coming generations?\n" + "I think of heaven and earth, without limit, without end,\n" + "And I am all alone and my tears fall down."; Stream<String> lines = Arrays.stream(poetry.split("\n")); Stream<String> words = lines.flatMap(line -> Arrays.stream(line.split(" "))); List<String> l = words.map( w -> { if (w.endsWith(",") || w.endsWith(".") || w.endsWith("?")) return w.substring(0,w.length() -1).trim().toLowerCase(); else return w.trim().toLowerCase(); }).distinct().sorted().collect(Collectors.toList()); System.out.println(l); //[ages, all, alone, am, and, are, before, behind, coming, down, earth, end, fall, generations, gone, have, heaven, i, limit, me, my, of, tears, that, the, think, where, without]
flatMapToDouble、flatMapToInt、flatMapToLong提供了转换成特定流的方法。
limit方法指定数量的元素的流。对于串行流,这个方法是有效的,这是由于它只需返回前n个元素便可,可是对于有序的并行流,它可能花费相对较长的时间,若是你不在乎有序,能够将有序并行流转换为无序的,能够提升性能。
List<Integer> l = IntStream.range(1,100).limit(5) .boxed() .collect(Collectors.toList()); System.out.println(l);//[1, 2, 3, 4, 5]
peek方法方法会使用一个Consumer消费流中的元素,可是返回的流仍是包含原来的流中的元素。
可实现与forEach相同的功能,可是forEach是terminal 操做,执行后流就结束了,使用peek会返回原来的流。
String[] arr = new String[]{"a","b","c","d"}; Arrays.stream(arr) .peek(System.out::println) //a,b,c,d .count();
sorted()
将流中的元素按照天然排序方式进行排序,若是元素没有实现Comparable,则终点操做执行时会抛出java.lang.ClassCastException异常。 sorted(Comparator<? super T> comparator)
能够指定排序的方式。
对于有序流,排序是稳定的。对于非有序流,不保证排序稳定。
String[] arr = new String[]{"b_123","c+342","b#632","d_123"}; List<String> l = Arrays.stream(arr) .sorted((s1,s2) -> { if (s1.charAt(0) == s2.charAt(0)) return s1.substring(2).compareTo(s2.substring(2)); else return s1.charAt(0) - s2.charAt(0); }) .collect(Collectors.toList()); System.out.println(l); //[b_123, b#632, c+342, d_123]
skip返回丢弃了前n个元素的流,若是流中的元素小于或者等于n,则返回空的流。
public boolean allMatch(Predicate<? super T> predicate) public boolean anyMatch(Predicate<? super T> predicate) public boolean noneMatch(Predicate<? super T> predicate)
这一组方法用来检查流中的元素是否知足断言。
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方法返回流中的元素的数量。它实现为:
mapToLong(e -> 1L).sum();
<R,A> R collect(Collector<? super T,A,R> collector) <R> R collect(Supplier<R> supplier, BiConsumer<R,? super T> accumulator, BiConsumer<R,R> combiner)
使用一个collector执行mutable reduction操做。辅助类Collectors提供了不少的collector,能够知足咱们平常的需求,你也能够建立新的collector实现特定的需求。它是一个值得关注的类,你须要熟悉这些特定的收集器,如聚合类averagingInt、最大最小值maxBy minBy、计数counting、分组groupingBy、字符串链接joining、分区partitioningBy、汇总summarizingInt、化简reducing、转换toXXX等。
第二个方法提供了更底层的功能,它的逻辑相似下面的伪代码:
R result = supplier.get(); for (T element : this stream) accumulator.accept(result, element); return result;
例子:
List<String> asList = stringStream.collect(ArrayList::new, ArrayList::add, ArrayList::addAll); String concat = stringStream.collect(StringBuilder::new, StringBuilder::append, StringBuilder::append) .toString();
findAny()
返回任意一个元素,若是流为空,返回空的Optional
,对于并行流来讲,它只须要返回任意一个元素便可,因此性能可能要好于findFirst()
,可是有可能屡次执行的时候返回的结果不同。 findFirst()
返回第一个元素,若是流为空,返回空的Optional。
forEach
遍历流的每个元素,执行指定的action。它是一个终点操做,和peek方法不一样。这个方法不担保按照流的encounter order顺序执行,若是对于有序流按照它的encounter order顺序执行,你可使用forEachOrdered
方法。
Stream.of(1,2,3,4,5).forEach(System.out::println);
max返回流中的最大值, min返回流中的最小值。
reduce是经常使用的一个方法,事实上不少操做都是基于它实现的。 它有几个重载方法:
pubic Optional<T> reduce(BinaryOperator<T> accumulator) pubic T reduce(T identity, BinaryOperator<T> accumulator) pubic <U> U reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner)
第一个方法使用流中的第一个值做为初始值,后面两个方法则使用一个提供的初始值。
Optional<Integer> total = Stream.of(1,2,3,4,5).reduce( (x, y) -> x +y); Integer total2 = Stream.of(1,2,3,4,5).reduce(0, (x, y) -> x +y);
值得注意的是accumulator应该知足结合性(associative)。
将流中的元素放入到一个数组中。
concat用来链接类型同样的两个流。
public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)
toArray
方法将一个流转换成数组,而若是想转换成其它集合类型,须要调用collect方法,利用Collectors.toXXX
方法进行转换:
public static <T,C extends Collection<T>> Collector<T,?,C> toCollection(Supplier<C> collectionFactory) public static ... toConcurrentMap(...) public static <T> Collector<T,?,List<T>> toList() public static ... toMap(...) public static <T> Collector<T,?,Set<T>> toSet()