Java函数式编程-3.流(Stream)

原创
01/17 19:08
阅读数 1.4K
 List<String> words = Arrays.asList("hello","world");
 List<String> chars = words.stream()
     .flatMap(word ->  Stream.of(word.split("")))
     .collect(Collectors.toList());

   Jdk8中新增的特性旨在帮助程序员写出更好的代码,其中对核心类库的改进是很关键的一部分。对核心类库的改进主要包括集合类的API和新引入的流(Stream)。流使程序员得以站在更高的抽象层次上对集合进行操作。

1.什么是流(Stream)?

Stream是特定类型的对象形成的一个队列并支持聚合操作。 Java中的Stream并不会存储元素,而是按需计算。流的来源可以是集合,数组,I/O channel, 产生器generator 等。聚合操作类似SQL语句一样的操作, 比如filter, map, reduce, find, match, sorted等。和以前的Collection操作不同, Stream操作还有两个基础的特征:Pipelining中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道,如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
在 Java 8 中, 集合接口有两个方法来生成流:

  • stream() − 为集合创建串行流。
  • parallelStream() − 为集合创建并行流。

List<String> list = Arrays.asList("a", "b", "c", "d", null);
List<String> filtered = list.stream().filter(e -> Objects.nonNull(e)).collect(Collectors.toList());
long count = list.parallelStream().filter(e -> Objects.nonNull(e)).count();

2.内部迭代

Java程序员在使用集合时,一个通用的模式时在集合上进行迭代,在迭代过程中处理每一个元素并返回处理结果。通常代码时这样的:

 List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
 int sum = 0;
 Iterator<Integer> iterator = list.iterator();
 while (iterator.hasNext()) {
    sum += iterator.next();
 }
System.out.println(sum);

这段代码本身没什么问题就写起来有点麻烦,就其背后原理来看其实就是一个封装了迭代的语法糖,首先调用iterator方法产生一个新的Iterator对象进而控制整个迭代过程,这就是外部迭代。

然而外部迭代的问题在于首先,很难抽象出后续提到的复杂操作,另外从本质上来讲只是一种串行化操作。总体来看使用for循环会将行为和方法混为一谈。另一种方法是内部迭代,和调用iterator()作用一样使用stream()方法,该方法不是返回一个控制迭代的Iterator对象,而是返回内部迭代中的相应接口:Stream。示例如下:

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Integer sum = list.stream().filter(e -> e > 5).reduce(0, Integer::sum);
System.out.println(sum);

3.实现机制

通常在Java 中调用一个方法计算机会随即执行操作:比如,System.out.println("hello world!")会在终端上输出一条信息。Stream里的一些方法略有不同,它们虽是普通Java方法但是返回的Stream对象并不是一个新的集合,而是创建新集合的配方。

list.stream().filter(e -> e > 5);

上述代码并未做什么实际性的工作,filter只是刻画出了Stream但是并没有产生新的集合,像filter这样只描述Stream,最终不产生新集合的方法惰性求值方法,而像sum()这样最终从Stream产出值的方法叫做及早求值方法。如果在filter方法中添加一条输出语句就会比较清楚的看出差异。

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
list.stream().filter(e -> {
            System.out.println(e);
            return e > 5;
          });

如果将上述语句添加一个及早求值方法就可以看到集合中的元素被打印出来。

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
list.stream().filter(e -> {
            System.out.println(e);
            return e > 5;
        }).findFirst();

判断一个操作是惰性求值还是及早求值很简单,只要返回值是Stream那么是惰性求值。使用这些操作的理想方式就是形成一个惰性求值链,最后用一个及早求值的操作返回想要的结果,这正是他的合理之处。那为什么要区分惰性求值和及早求值呢?原因很简单只需要一次迭代就可以得到全部结果,例如 :上述代码只需要找到第一个大于5的数而不需要比较所有集合元素。

4.常用的流操作

为了更好的学习Stream,我们接下来学习一些常用的stream操作。

  •  filter

如果需要在遍历集合元素时清洗非法元素可以使用filter方法,我们在上述讨论过程中已经使用过该方法。filter接收一个函数作为参数,该函数使用lambda表达式表示,如果被清洗元素符合预期则返回true,其实看源码会发现这个lambda表达式是我们之前讲过的 Predicate<T>函数。

  • collect

这个方法是由Stream生成一个List,是一个及早求值操作。Jdk8中借鉴了很多Scala的设计思想,Stream的of方法可以用来生成一个新的Stream,我们来看下边的一个例子。

List<String> list = Stream.of("a", "b", "c","d","e").collect(Collectors.toList());
  • map

map函数是用来讲一个类型转换为另外一种类型,参数同样是一个lambda表达式对应我们之前讲过的 Function<T, R> 函数。因为map是一个惰性求值方法我们配合之前的collect看一个例子。

  List<String> list = Stream.of("a", "b", "c").map(e -> e.toUpperCase()).collect(Collectors.toList());
  System.out.println(list);
  • flatMap

和map类似,不同的是其每个元素转换得到的是Stream对象,最终会把转换得到的多个Stream连接成一个Stream。

 List<String> words = Arrays.asList("hello","world");
 List<String> chars = words.stream()
     .flatMap(word ->  Stream.of(word.split("")))
     .collect(Collectors.toList());

上述代码我们可以得到输出结果 :[h, e, l, l, o, w, o, r, l, d],如果将flatMap换成map会发现我们得到了一个List<Stream<String>> ,大家感兴趣可以上手一试。

  • max和min

和之前讲的函数不同,这两个方法要求参数是一个比较器 Comparator<T> 

Integer min = Stream.of(1,2,3,4,5).min(Integer::compare).get();
System.out.println(min);
Integer max = Stream.of(1,2,3,4,5).max(Integer::compare).get();
System.out.println(max);
  • reduce

该函数可以实现从一组值中生成一个值,在上述例子中用到的 sum、max、min本质上都是reduce操作。Stream的求和结果每一步都将Stream中的元素累加至accumulator,遍历至Stream中的最后一个元素时,accumulator的值就是所有元素的和。我们看如下例子

 Integer sum = Stream.of(1, 2, 3, 4, 5).reduce(0, (acc, e) -> acc + e);
 System.out.println(sum);

这段代码如果你对scala熟悉那么会很容易理解,acc作为累加器存储了中间计算结果,整个lambda函数本质上是我们之前讲过的BinaryOperator。

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部