Java8 Stream介绍

前言

Java8在我看来主要由2个大的特性,一个是lambda的使用,另一个是stream流的出现,该篇博文用于介绍stream流的常用操作符。

流和其它集合具体的区别

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

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

创建Stream

可以通过多种方式创建流:

  1. 通过集合的stream()方法或者parallelStream(),比如Arrays.asList(1,2,3).stream()
  2. 通过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类的操作路径的方法,如listfindwalk等。
  6. 随机数流Random.ints()
  7. 其它一些类提供了创建流的方法,如BitSet.stream(),Pattern.splitAsStream(java.lang.CharSequence)JarFile.stream()
  8. 更底层的使用StreamSupport,它提供了将Spliterator转换成流的方法。

这里不过多介绍,使用最广泛的就是直接通过集合.stream()来获取流的

中间操作 intermediate operations

distinct

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

1
2
3
4
5
List<String> l = Stream.of("a","b","c","b")
.distinct()
.collect(Collectors.toList());
// [a, b, c]
System.out.println(l);

 

filter

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

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

1
2
3
4
5
6
List<Integer> l = IntStream.range(1,10)
.filter( i -> i % 2 == 0)
.boxed()
.collect(Collectors.toList());
// [2, 4, 6, 8]
System.out.println(l);

 

map

map方法将流中的元素映射成另外的值,新的值类型可以和原来的元素的类型不同。

下面的代码中将字符元素映射成它的哈希码(ASCII值)。

1
2
3
4
5
List<Integer> l = Stream.of('a','b','c')
.map( c -> c.hashCode())
.collect(Collectors.toList());
// [97, 98, 99]
System.out.println(l);

 

flatmap

flatmap方法混合了map + flattern的功能,它将映射后的流的元素全部放入到一个新的流中。它的方法定义如下:

1
<R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper)

 

可以看到mapper函数会将每一个元素转换成一个流对象,而flatMap方法返回的流包含的元素为mapper生成的所有流中的元素。

下面这个例子中将一首唐诗生成一个按行分割的流,然后在这个流上调用flatmap得到单词的小写形式的集合,去掉重复的单词然后打印出来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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());
// [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]
System.out.println(l);

 

flatMapToDoubleflatMapToIntflatMapToLong提供了转换成特定流的方法。

limit

limit方法指定数量的元素的流。对于串行流,这个方法是有效的,这是因为它只需返回前n个元素即可,但是对于有序的并行流,它可能花费相对较长的时间,如果你不在意有序,可以将有序并行流转换为无序的,可以提高性能。

1
2
3
4
5
List<Integer> l = IntStream.range(1,100).limit(5)
.boxed()
.collect(Collectors.toList());
// [1, 2, 3, 4, 5]
System.out.println(l);

 

peek

peek方法方法会使用一个Consumer消费流中的元素,但是返回的流还是包含原来的流中的元素。

1
2
3
4
5
String[] arr = new String[]{"a","b","c","d"};
Arrays.stream(arr)
// a,b,c,d
.peek(System.out::println)
.count();

 

sorted

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

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

1
2
3
4
5
6
7
8
9
10
11
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());
// [b_123, b#632, c+342, d_123]
System.out.println(l);

 

skip

skip返回丢弃了前n个元素的流,如果流中的元素小于或者等于n,则返回空的流。

终点操作terminal operations

Match

1
2
3
public boolean 	allMatch(Predicate<? super T> predicate)
public boolean anyMatch(Predicate<? super T> predicate)
public boolean noneMatch(Predicate<? super T> predicate)

这一组方法用来检查流中的元素是否满足断言。

  1. allMatch只有在所有的元素都满足断言时才返回true,否则flase,流为空时总是返回true
  2. anyMatch只有在任意一个元素满足断言时就返回true,否则flase
  3. noneMatch只有在所有的元素都不满足断言时才返回true,否则flase
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    //true
    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));
    //false
    System.out.println(Stream.of(1,2,3,4,5).noneMatch( i -> i > 0));
    //true
    System.out.println(Stream.<Integer>empty().allMatch( i -> i > 0));
    //false
    System.out.println(Stream.<Integer>empty().anyMatch( i -> i > 0));
    //true
    System.out.println(Stream.<Integer>empty().noneMatch( i -> i > 0));

count

count方法返回流中的元素的数量。它实现为:

1
mapToLong(e -> 1L).sum();

 

collect

1
2
<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等。

主要使用的是toListtoMap,其他的暂时还未使用到

第二个提供了更底层的功能,它的逻辑类似下面的伪代码:

1
2
3
4
R result = supplier.get();
for (T element : this stream)
accumulator.accept(result, element);
return result;

 

例子:

1
2
List<String> asList = stringStream.collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
String concat = stringStream.collect(StringBuilder::new, StringBuilder::append, StringBuilder::append).toString();

 

find

findAny()返回任意一个元素,如果流为空,返回空的Optional,对于并行流来说,它只需要返回任意一个元素即可,所以性能可能要好于findFirst(),但是有可能多次执行的时候返回的结果不一样。
findFirst()返回第一个元素,如果流为空,返回空的Optional

forEachforEachOrdered

forEach遍历流的每一个元素,执行指定的action。它是一个终点操作,和peek方法不同。这个方法不担保按照流的encounter order顺序执行,如果对于有序流按照它的encounter order顺序执行,你可以使用forEachOrdered方法。

1
Stream.of(1,2,3,4,5).forEach(System.out::println);

maxmin

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

reduce

reduce是常用的一个方法,事实上很多操作都是基于它实现的。
它有几个重载方法:

1
2
3
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)

 

第一个方法使用流中的第一个值作为初始值,后面两个方法则使用一个提供的初始值。

1
2
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)。

toArray()

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

组合

concat用来连接类型一样的两个流。

1
public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)

 

转换

toArray方法将一个流转换成数组,而如果想转换成其它集合类型,西需要调用collect方法,利用Collectors.toXXX方法进行转换:

1
2
3
4
5
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()

 

原文作者:文/鸟窝
原文标题:Java Stream 详解

posted @ 2019-07-31 15:51  qxwang  阅读(41)  评论(0)    收藏  举报